← Все статьи

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 с при полном отсутствии фреймворка — для статически выглядящего сайта это неожиданно болезненно. Автор зафиксировал семь находок в отдельном документе (диагностика отдельно от фиксов) и закрывал их по приоритету: быстрые победы сначала, архитектурные — потом.

Среди находок: висящий 29,8 с beacon Google Ads Audiences через 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.