Содержание
Коротко
Автор убрал 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.
На практике
- Измеряйте дисперсию — несколько прогонов 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.