← Усі статті

Zero-JS замало: кеш SSR-HTML на edge через Cloudflare Workers

Як автор стабілізував Core Web Vitals після відмови від React: Cache API, cookie-gated HTML, відмова від Zod у браузері та рефакторинг форм на Astro.

Зміст

Коротко

Автор прибрав 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.

На практиці

  1. Вимірюйте дисперсію — кілька прогонів PageSpeed/GTmetrix поспіль; якщо TBT = 0, а Performance стрибає, дивіться TTFB і root document.
  2. Кешуйте анонімний HTML у Workercache.match до middleware-ланцюжка; персоналізація за явним списком cookie.
  3. Не змішуйте Set-Cookie і edge-кеш — синхронізацію lang-cookie лише коли cookie вже є і не збігається з URL.
  4. Аудит third-party — audience sync може тримати Fully Loaded десятки секунд без користі для продукту.
  5. Zod на сервері, легке дзеркало в браузері — один інтерфейс safeParse, ті самі коди помилок; сервер лишається джерелом істини.
  6. Ліниве завантаження прихованих кроків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.