Contents
In brief
The author removed React and hydration, reached Total Blocking Time = 0 ms, yet PageSpeed still swung between 90 and 100. The culprit was server-side: every request rendered fresh HTML on Cloudflare Workers. The fix — middleware using the Cache API, personalization gated on two cookies, plus a series of small wins around forms and CSP.
What happened
The story opens with a confession: a zero-JS build eliminated main-thread blocking, but lab PageSpeed runs still jittered. Four measurements in ten minutes returned 96, 100, 98, 96 — with no deploy. The variable was not in the browser but in HTML generation time at the edge.
GTmetrix showed a root document up to 1.2 s with no framework at all — surprisingly painful for a site that looks static. The author logged seven findings in a dedicated document (diagnosis separate from fixes) and closed them by priority: quick wins first, architecture last.
Among the findings: a 29.8 s Google Ads Audiences beacon via Zaraz (disabled with one checkbox), Zod v4 using new Function() in the client bundle (breaking CSP without 'unsafe-eval'), a hidden subscribe step that still shipped JS on first paint, and a select forcing reflow on every resize.
The main architectural move — htmlCacheMiddleware: anonymous GET requests without personalization cookies are cached in caches.default for 300 s; responses with newsletter/consent cookies always bypass cache. Key = origin + pathname (locale in path, query stripped). After shipping, root document with X-Edge-Cache: HIT landed around ~140 ms instead of 1.2 s.
Why it matters
Many teams optimize only the client: drop JS, split bundles, tune CLS. But with SSR on Workers, TTFB and render variance hit LCP and lab score stability directly. A Cache-Control header on HTML is often useless when run_worker_first is on: the Worker answers before CDN cache layers, so the only HTML cache is the one the Worker checks itself.
The article is a useful edge-native case study: how not to poison cache with Set-Cookie (cookie-less request → cookie-less response), how to split edge TTL (s-maxage only on stored copies) from browser policy (private, no-cache on all HTML responses), and how to replace heavy client validation with a ~20-line mirror while keeping server Zod as source of truth.
In practice
- Measure variance — several PageSpeed/GTmetrix runs back-to-back; if TBT = 0 but Performance swings, inspect TTFB and root document time.
- Cache anonymous HTML in the Worker —
cache.matchbefore the middleware chain; personalize only via an explicit cookie list. - Never mix Set-Cookie and edge cache — sync lang cookie only when it already exists and disagrees with the URL.
- Audit third parties — audience sync can hold Fully Loaded for tens of seconds with zero product value.
- Zod on server, light mirror in browser — one
safeParseinterface, same error codes; server stays authoritative. - Lazy-load hidden steps —
dynamic importthe segmentation controller only after a successful subscribe.
| Finding | Effect after fix |
|---|---|
| No edge HTML cache | Root ~140 ms (HIT) |
| GA Audiences beacon | Fully Loaded down from 31.6 s |
| Zod in client | Best Practices 100, no eval in CSP |
| Newsletter domain refactor | Less JS on first screen |
Takeaway
Zero-JS is necessary but not sufficient for stable Core Web Vitals on SSR at the edge. Cookie-gated Cache API, strict CSP, and form discipline brought medians around ~100 mobile / 98 desktop — with the understanding that lab noise never fully disappears. If your site runs on Workers with i18n and personalization, walk through the article’s checklist before the next “magic” framework deploy.