Зміст
Коротко
Автор прибрав React і гідрацію, досяг Total Blocking Time = 0 ms, але PageSpeed усе одно стрибав між 90 і 100. Причина виявилася на сервері: кожен запит рендерив свіжий HTML на Cloudflare Workers. Рішення — middleware з Cache API, персоналізація лише за двома cookie та серія дрібних правок навколо форм і CSP.
Що сталося
Історія починається з чесного визнання: zero-JS збірка прибрала блокування головного потоку, але лабораторні прогони PageSpeed досі «танцювали». Чотири заміри за десять хвилин дали 96, 100, 98, 96 — без деплою. Змінна була не в браузері, а в часі генерації HTML на edge.
GTmetrix показав кореневий документ до 1,2 с при повній відсутності фреймворка — для статично виглядає сайту це несподівано болісно. Автор зафіксував сім знахідок в окремому документі (діагностика окремо від фіксів) і закривав їх за пріоритетом: швидкі перемоги спочатку, архітектурні — потім.
Серед знахідок: beacon Google Ads Audiences на 29,8 с через Zaraz (вимкнули однією галочкою), Zod v4 з new Function() у клієнтському бандлі (ламав CSP без 'unsafe-eval'), прихований крок підписки, який усе одно тягнув JS на перший екран, і select із примусовим reflow на кожен resize.
Головний архітектурний крок — middleware htmlCacheMiddleware: анонімні GET-запити без cookie персоналізації кешуються в caches.default на 300 с; відповідь з cookie newsletter/consent завжди йде повз кеш. Ключ — origin + pathname (locale в шляху, query відкидається). Після викладки кореневий документ з X-Edge-Cache: HIT — ~140 ms замість 1,2 s.
Чому це важливо
Багато команд оптимізують лише клієнт: прибирають JS, ріжуть бандли, налаштовують CLS. Але при SSR на Workers TTFB і дисперсія рендера напряму б'ють по LCP і стабільності lab-оцінок. Заголовок Cache-Control на HTML часто марний, якщо увімкнено run_worker_first: Worker відповідає до шару CDN-кешу, і єдиний кеш HTML — той, який Worker перевіряє сам.
Стаття корисна як кейс «edge-native» сайту: як не отруїти кеш Set-Cookie (cookie-less запит → cookie-less відповідь), як розділити edge TTL (s-maxage лише в stored copy) і браузерний (private, no-cache для всіх HTML-відповідей), і як замінити важку клієнтську валідацію дзеркалом на ~20 рядків без втрати узгодженості з серверним Zod.
На практиці
- Вимірюйте дисперсію — кілька прогонів PageSpeed/GTmetrix поспіль; якщо TBT = 0, а Performance стрибає, дивіться TTFB і root document.
- Кешуйте анонімний HTML у Worker —
cache.matchдо middleware-ланцюжка; персоналізація за явним списком cookie. - Не змішуйте Set-Cookie і edge-кеш — синхронізацію lang-cookie лише коли cookie вже є і не збігається з URL.
- Аудит third-party — audience sync може тримати Fully Loaded десятки секунд без користі для продукту.
- Zod на сервері, легке дзеркало в браузері — один інтерфейс
safeParse, ті самі коди помилок; сервер лишається джерелом істини. - Ліниве завантаження прихованих кроків —
dynamic importконтролера сегментації лише після успішної підписки.
| Знахідка | Ефект після фікса |
|---|---|
| Немає edge-кеша HTML | Root ~140 ms (HIT) |
| GA Audiences beacon | Fully Loaded з 31,6 s |
| Zod у клієнті | Best Practices 100, без eval у CSP |
| Рефакторинг newsletter domain | Менше JS на першому екрані |
Підсумок
Zero-JS — необхідна, але недостатня умова стабільних Core Web Vitals на SSR-edge. Cookie-gated Cache API, сувора CSP і дисципліна навколо форм дали автору медіани ~100 mobile / 98 desktop з розумінням, що lab-шум повністю не зникне. Якщо ваш сайт на Workers з i18n і персоналізацією — чеклист зі статті варто пройти до наступного «магічного» деплою React.