I spent two days dragging the /intro page from “fine” to ~92 mobile Lighthouse, and most of the work was undoing things I’d added for aesthetics or “security.” The interesting part isn’t the final number — it’s how many of these wins came from deleting code.
The watermark script was wrecking CLS
The worst offender was a security.js function called addWatermarkToImages. It ran on every image after load, injected wrapper elements, and mutated inline styles. Net effect: CLS 0.19 and, embarrassingly, the wrong brand name on the watermark. I removed it entirely (7b4bde91). No layout shift, no wrong logo, less JS. There was no version of this that was worth keeping — a client-side DOM watermark on a static marketing page is theater, not security.
The rest of security.js kept fighting me. It did inline style mutations that extended LCP, and it ran an idle-time querySelectorAll over the whole document. I deferred the whole thing to the load event so it can’t touch anything in the critical path (8cdd5ef8, a8bfffd6), then removed the idle-time scan outright (ad882242). Rule of thumb that kept proving true: anything that mutates the DOM after first paint is a CLS/LCP liability until proven otherwise.
The hero image: AVIF lost
The hero started as a CSS background-image, which is the classic LCP mistake — the browser can’t prioritize a background. I switched it to a real <picture> with fetchpriority="high" and a 480w carousel variant (bf77b9c2).
Then the AVIF question. I had AVIF + WebP sources, AVIF being smaller. But for the LCP element specifically the decode cost matters more than transfer size on mobile. AVIF decode latency was pushing the paint later than the slightly-larger WebP did. I dropped AVIF from the hero <picture> and kept WebP only (1c4c3c69). AVIF still wins for below-the-fold posters where decode time is invisible — this was a targeted call for the one element on the critical path.
I also went back and forth on decoding. Tried decoding="sync" to align LCP with FCP (b94d73b6), then reverted to auto (65579657) once it turned out sync was just blocking paint without moving the LCP timestamp meaningfully. Two commits to confirm the default was right — that’s the cost of measuring instead of guessing.
Grain texture, three attempts
The body::before grain overlay was a real LCP drag because it was a background-image data URI the browser fetched and painted early. First I replaced it with an inline SVG filter (c9860dab), then switched from background-image to mask-image (6f6a162c) so the grain became a compositor-level effect over a solid color instead of a paint-time texture. Eventually, for the intro pages, I just removed the grain above the fold entirely (a2b57c3a) — it wasn’t worth a single millisecond of LCP on the page that actually gets traffic.
Deferring third-party JS
Two more critical-path offenders: gtag/js and again security.js. Both went to the load event (938f240e). Analytics has no business executing before the LCP paint. This is obvious in hindsight but the default GA snippet injects in <head> and people leave it there.
Critical CSS, and the CLS it reintroduced
The final push was inlining critical above-fold CSS and deferring the full stylesheet (a2b57c3a). This is the standard trick, but it bit me immediately: my first critical-CSS extraction was incomplete, so above-the-fold elements rendered with partial styles and then shifted when the deferred CSS landed — a CLS regression I caused while chasing LCP. I had to go back and complete the above-fold rules so the inlined set fully described everything visible on first paint (2795739e). Critical CSS is only a win if it’s actually complete; a partial set just trades a paint delay for a layout shift.
Caching the HTML
The one pure infra win: s-maxage=3600 in the middleware (ddf3bea9) so Cloudflare caches the rendered HTML at the edge. These are static marketing pages — there’s no reason for every request to hit origin. s-maxage (shared cache only) rather than max-age keeps browsers from over-caching while letting the CDN serve repeats.
What it added up to
88 → ~92 average mobile (3f081fde). Modest on paper. But the breakdown is the lesson: the biggest single fix was deleting the watermark script, not any clever optimization. Most of my LCP wins were removing things — AVIF decode, background-image grain, background-image hero, head-blocking analytics — that I’d added without measuring their cost on the critical path. The clever bits (mask-image, critical CSS) were smaller and riskier, and the critical CSS one actively backfired before it helped.