- Shipped
- August 19, 2026 at 5:29 PM UTC
- Author
- Kamo
- Commit
- 917ad87
The homepage shipped its whole fold invisible. Framer Motion serialises an `initial` prop into the prerendered HTML as an inline style, so the markup arrived with the <h1>, the copy column, the reel aperture and the full-bleed background all at `opacity: 0` — and stayed that way until React had downloaded, parsed and hydrated and run its first animation frame. First and Largest Contentful Paint were therefore measuring a quarter-megabyte JavaScript bundle rather than the page: the server had already rendered the headline, and a script was holding it back. Nothing sees this — not the build, the type check, the lint run or the navigation guard — and on a desktop link it is invisible. `curl` the page and count `opacity:0`: 102 of them, every element above the fold among them. The same entrances, at the same durations and easing, are now CSS keyframes. The stylesheet is parsed before a single script has been fetched. The LCP background uses a transition rather than a keyframe, because a transition does not run on the initial render — so the frame that is up on arrival is simply up, with nothing for LCP to wait on. The rest of the load window, in descending order of what it cost: · The reel started paying from the first byte — `preload="metadata"`, a poster on the element, and an adaptive ladder opening on the 540p rung at 1.17 Mbps — for an aperture that on a narrow viewport sits below the fold, under the copy column. It now waits for the load event, then for the main thread to go idle, then for the aperture to come within a viewport. Level is capped to the player's size and starts on the bottom rung on a phone. Save-Data and 2G/3G readers keep the poster frame and the restart button. · The poster itself was a 93 KB JPEG on a one-day cache header. Through the image optimiser it is a 26 KB AVIF on an immutable URL, and it is not fetched until the reel is wanted. · The brand logo is 132 KB of eight-decimal potrace output, preloaded as an image, to draw a mark 140 px wide. Rounded to one decimal of a 3503-unit space with the repeated fill hoisted, it is 59 KB — geometrically identical to 0.0023 CSS px at the largest size it is ever rendered at. scripts/ optimize-logo-svg.mjs is idempotent, for the next time the logo is exported. · GSAP was on the eager script list of every page in the site, four times over: a tween on the progress bar's glow, `gsap.set()` writing two static style properties, a `fromTo` duplicating a ripple the stylesheet was already animating, and a global-timeline call that only fires for readers who have asked for reduced motion. All four are gone from the critical path; the last loads the library inside its branch. · TouchOptimized measured every button, link and input on the page during hydration, interleaving each read with the previous element's writes — a forced-synchronous-layout loop several hundred elements long, landing in Total Blocking Time on the phone it exists to help. It now batches the reads and waits for idle. · `<Script strategy="afterInteractive">` emits a `<link rel="preload" as="script">`, which asked the browser to fetch the analytics tracker at high priority alongside the hero background. `lazyOnload` still records the visit, a moment later and off the critical path. · The hero mounted three full-viewport background layers. The rotation is desktop-only, so on a phone two of them were permanently `opacity: 0`. · `clearCookieConsent` — two lines of localStorage — was exported from the consent banner, so the footer's "manage cookies" control dragged 250 lines of Framer Motion into a chunk that renders in the initial HTML. It moves to app/lib/cookie-consent.ts, and the banner and the toast stack now mount on idle: neither paints anything on arrival. · `experimental.optimizeCss` printed "✓ optimizeCss" in every build and did nothing. It inlines critical CSS by post-processing finished HTML, which the App Router never hands it. Verified against the built output with the `beasties` backend actually installed: zero <style> blocks in <head>, the stylesheet still a plain blocking <link>. Removed — a flag that reports success while inert reads as an optimisation already taken. · 36 class selectors and 24 keyframes in globals.css with no reference anywhere in the repository, vendor and docs included. Critical-path bytes, gzip, deployed against a clean build: html 38,101 → 36,277 js 281,700 → 245,707 css 18,629 → 17,398 logo 57,390 → 22,902 total 395,820 → 322,284 (-19%) eager scripts 15 → 13 Out of the load window entirely on top of that: the 93 KB poster, the HLS manifest and its 1.17 Mbps segments, two background images, and the tracker. Not addressed here: `/` answers 307 to `/en`, which is the URL a page-speed run tests and which Lighthouse counts as a redirect. The only real fix is `localePrefix: 'as-needed'`, and that rewrites canonicals, hreflang, sitemaps and every internal href across ~250 routes in 22 locales while 301-ing every indexed /en/* URL. That is an SEO decision rather than a performance one, and the hop costs ~60 ms on an already-open connection.