Привет, Хабр!
Вы, наверно, привыкли к стандартным HTTP-ответам – 200, 301, 404, 500 и т. д. А тут подкрался новый статус 103 – Early Hints. Это небольшой пинок браузеру: сервер шлет код 103 с заголовками Link: rel=preload ещё до того, как сформировал основной HTML. Пока бэкенд думает над ответом, браузер параллельно начинает грузить критические ресурсы (CSS, JS, шрифты и т. д.). Звучит просто, но эффект на производительность и LCP может быть весьма значительным.
Зачем это нужно для LCP и веб-перформанса
Никому не нравится ждать. Особенно когда ждали – а наконец-то на экране отображается главный контент (изображение, большой заголовок, баннер и т.п.). В метриках Core Web Vitals время до показа крупнейшего элемента страницы очень важно, и Early Hints может заметно улучшить этот показатель.
Кроме LCP, сразу начатая загрузка полезна и для других метрик: быстрее идёт First Contentful Paint и сокращается кажущееся время до полной загрузки страницы. Фактически дополнительные миллисекунды загрузки разбросанных скриптов и стилей перестают попадать в путь рендеринга.
Для NGINX внедрение Early Hints даётся практически без допиливаний нашего приложения. Вся настройка происходит на уровне веб-сервера – никакие роуты или хэндлеры в бэкенде трогать не придется (ну или почти не придется). Проще говоря, это почти бесплатный прирост LCP: нужны только правки в конфиге NGINX, а не переписывание логики выдачи страниц.
Поддержка Early Hints в браузерах и серверах
Хорошая новость: все современные браузеры уже понимают 103 Early Hints. Chrome, Safari и Edge официально поддерживают этот код (точнее, поведение с preloading). В Firefox тоже движутся в эту сторону (примеры из devtools уже есть). А крупные CDN/провайдеры (Cloudflare, Fastly, Akamai и прочие) тоже дают ранние подсказки за нас, если активировать их фичи. Но мы сейчас не о них, а о NGINX.
С точки зрения серверов: Apache (mod_http2), H2O, Node.js (v18+) и другие уже умеют отдавать 103 или эквивалентные механизмы. В NGINX поддержка появилась с версии 1.29.0 (mainline). Именно в этой версии был введён новый директив early_hints, который управляет отправкой 103 от обратного прокси или, в будущем, и сам по себе.
Важно помнить, что 103 – это информационный статус. Старые клиенты (особенно HTTP/1.1-браузеры, которые не ждут 103) могут сломаться: они воспримут дополнительный ответ за протоколльную ошибку. Поэтому универсально включать 103 нельзя. Нужен контроль: отсылаем ранние подсказки только тем клиентам, которые их поймут и действительно получат от них выгоду.
Настройка NGINX для ранних подсказок
Для начала убедитесь, что у вас NGINX версии 1.29.0 или новее. Если вы на стабильной ветке ниже — можно либо перейти на mainline, либо собрать NGINX из исходников с патчем (о нём ниже). Предположим, версии хватило. Теперь про конфигурацию.
Идея в том, чтобы направлять 103-ответ на клиент только при навигации главной страницы и по протоколам HTTP/2 или HTTP/3. В NGINX есть переменные $http2 и $http3, которые равны непустой строке, когда соединение ведётся по H2/H3. И заголовок Sec-Fetch-Mode: navigate указывает, что запрос — это основная навигация, а не, скажем, загрузка подресурса или API. Общепринятая схема такая:
# Включаем отправку 103 только для запросов навигации по HTTP/2 или HTTP/3 map $http_sec_fetch_mode $early_hints { navigate $http2$http3; } server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/ssl/example.crt; ssl_certificate_key /etc/ssl/example.key; location / { # Активируем Early Hints по условию из map early_hints $early_hints; # Проксируем запрос к бэкенду proxy_pass http://backend\_upstream; # Пример: передаём в заголовках основной задачи # (сами Link-ы для ранних подсказок бэкенд должен выставлять) # Другие настройки proxy_pass как обычно... } }
map устанавливает переменную $early_hints в непустое значение (например, «1»), если Sec-Fetch-Mode равен navigate и протокол HTTP/2 или HTTP/3. В location говорим early_hints $early_hints, то есть отправлять подсказки, только если условие истинно. Таким образом у клиентов по HTTP/1.1 или при загрузке через AJAX/iframe никаких 103 даже не будет попытки: они просто не получат ответ 103.
Директива early_hints появилась именно в 1.29.0 и по дефолту отключена. Мы должны явно её включить (либо через on/off, либо как здесь – через непустую строку). Документация на это прямо говорит: если хоть один параметр early_hints непустой и не «0», то ранний ответ будет отправлен.
Передача подсказок из бэкенда
Если бэкенд (будь то Django, Rails, PHP и т. д.) уже умеет отдавать заголовки Link и 103, то NGINX просто проксирует их сквозь себя. То есть вы готовите обычный код в приложении:
HTTP/1.1 103 Early Hints Link: </static/app.css>; rel=preload; as=style Link: </static/app.js>; rel=preload; as=script ...затем текст страницы с 200 OK...
NGINX при proxy_pass увидит этот 103 (и его Link) и отправит клиенту сразу. Затем он дочитает основной ответ (200 OK) и вернёт полный HTML. Никаких сложных манипуляций с NGINX в таком случае не нужно: достаточно early_hints on; proxy_pass ....
Статические подсказки на уровне NGINX
А что если приложение никак не может выдать 103? Например, устаревший PHP или CMS без поддержки Early Hints. Тогда можно обойтись одним NGINX. В экспериментальном PoC для NGINX уже появились примеры статического добавления подсказок с помощью add_header. Формат такой (заметку: для этого нужна версия с патчем или пока актуален патч PoC):
location / { # Прямо отдаём ранние подсказки сразу с NGINX early_hints on; # Эти заголовки пойдут в 103 ответ # (Патч добавляет параметр 'early' к add_header) add_header Link "</static/app.css>; rel=preload; as=style" early; add_header Link "</static/app.js>; rel=preload; as=script" early; try_files $uri $uri/ =404; }
Вручную прописали Link для двух ресурсов. В патче слово early означает, что эти заголовки отправятся не в окончательном ответе, а именно в Early Hints. После этого, когда придёт запрос на страницу, NGINX сразу даст клиенту:
HTTP/2 103 Early Hints Link: </static/app.css>; rel=preload; as=style Link: </static/app.js>; rel=preload; as=script HTTP/2 200 OK Content-Type: text/html ...тело страницы...
Клиент, получив 103, моментально начинает загружать app.css и app.js по указанному пути, в то время как сервер ещё думает над HTML.
Практически это почти то же самое, что если бы бэкенд сам добавил 103. Но плюсы в том, что весь бэкенд здесь – сам NGINX. Недостаток: пока нужно поставить патч (или дождаться, когда это выйдет в стабильную ветку). Либо можно реализовать похожее через njs или stub-интерфейсы, но обычно проще поднять mainline-сборку.
Резюме по настройке: обновляем NGINX (1.29.0+), включаем early_hints в нужном месте (лучше в location главной страницы), настраиваем условия (HTTP/2/3 + Sec-Fetch-Mode), и снабжаем заголовками Link: rel=preload либо находим способ передать их от бэкенда. Больше писать в бэкенде не придётся, разве что организовать передачу нужных Link (их можно даже хранить в БД или генерировать автоматически по зависимостям).
Нюансы
-
HTTP/1.1. Старые браузеры, которые не ждут коды 103, просто не знают, что делать с дополнительным ответом. Поэтому включать подсказки для них опасно. Хорошая практика – проверять протокол, sec-fetch и т.д., как в примере выше. Если
mapдал пустую строку —early_hintsне сработает и клиент не увидит ничего лишнего. -
Навигация только. Early Hints предназначен только для навигационных запросов (
Sec-Fetch-Mode: navigate). Никакие AJAX-запросы, загрузка изображений в фоне, вызовы сmode: 'cors'и даже iFrame-подзапросы не получат 103 (и не должны). -
Содержимое подсказок. Важно не переусердствовать с hint-ами, чтобы не породить конфликт версий. Если вы предсказали
main.abcd100.css, а в основной HTML оказываетсяmain.abcd105.css, браузер зря потратит время (и диск/трафик). Поэтому даем только достаточно стабильные ресурсы: например, общий базовый стиль, фавикон, шрифт. Google советует не пытаться подтягивать «динамически генерируемые части» в 103. Разумно разбить ресурсы на «стабильную часть» (для подсказок) и «экспериментальную», которая подтянется в основном документе. -
Повторная линковка. Даже если мы отправили
Link: rel=preloadв 103, хорошо бы также указать эти же (и, возможно, дополнительно другие)Linkв окончательном ответе (200 OK) или в тегах<link rel=preload>HTML. Дело в том, что у части клиентов Early Hints может не сработать (например, если оно проскакивает плагин кеширования, CDN и т. д.), или если нужный ресурс не стал критическим. Поэтому продублируйте важные preload-ссылки в финальном HTML. Как раз из примера Google видно: после 103 и 200 OK оба содержатLink. -
HTTP/2 Push vs Early Hints. Если вы раньше пользовались HTTP/2 Server Push, знайте: Early Hints – более лёгкая альтернатива. При Push данные насильно шли от сервера, иногда избыточно. А Early Hints – только намёк браузеру, без навязывания. Современные браузеры благосклоннее воспринимают link preload, чем push. Но в любом случае, как показала практика, именно 103 умеет запускать загрузку прямо во время генерации страницы, а не после, как обычные
<link>в HTML. -
Отладка и проверка. Увидеть работу Early Hints можно в девтулзах (в сети включите HTTP/2, отключите кэш). В Chrome для ресурса-переключения у загруженных ранних подсказок будет пометка
(Disk cache)и инициаторearly-hints. Если без девтулз, можно сделатьcurl --http2 -i https://сайт/и поискать строку103 Early Hints. Бывает, curl сам схлопывает информационные коды, поэтому удобней DevTools или прокси типа mitmproxy.
Пример
Допустим, у нас простой сайт со статикой, и мы хотим заранее отправлять браузеру CSS и JS. Бэкэнд мы вносить не будем, используем патч NGINX. Конфигурация могла бы выглядеть так:
server { listen 443 ssl http2; server_name static.example.com; ssl_certificate /etc/ssl/static.crt; ssl_certificate_key /etc/ssl/static.key; location = / { # Отправляем 103 сразу early_hints on; add_header Link "</css/style.css>; rel=preload; as=style" early; add_header Link "</js/app.js>; rel=preload; as=script" early; # Собственно выдача страницы try_files /index.html =404; } }
Клиент получит при заходе на / заголовки типа:
HTTP/2 103 Early Hints Link: </css/style.css>; rel=preload; as=style Link: </js/app.js>; rel=preload; as=script HTTP/2 200 OK Content-Type: text/html ...тело index.html ...
И браузер в этот момент сразу грузит /css/style.css и /js/app.js. В итоге LCP (где, скажем, главный баннер или крупная графика зависят от этих стилей/скрипта) может появиться заметно раньше.
Результаты и эффект на LCP
Early Hints хорошо отрабатывает именно на самых популярных страницах и навигации (например, главная страница, страницы категории). Когда к вам идут люди и первый раз открывают сайт, у них нет ничего закешировано, поэтому каждый миллисекунд на счету. Как показывали различные тесты в инете, отдача в LCP измеряется сотнями миллисекунд. В одном эксперименте без 103 картинка LCP рендерилась на 45 % медленнее по времени, чем с подсказкой. В другом сравнивании разница составляла почти 1 секунду ускорения.
Для нас это означает: пользователи увидят «главную картинку» (или текст) раньше, еще до того как полностью дозагрузятся шрифты и стили. Даже если у вас и до этого был хороший LCP (например, ставили <link rel=preload> в HTML), Early Hints добавляет бонус за счёт параллельного начинания загрузок. И всё это — без дополнительной нагрузки на сервер в момент ответа, только время на передачу небольших заголовков 103.
Даже если вы умеете выжимать максимум из веб-сервера и доводить LCP до сотен миллисекунд, есть другая область, где скрываются узкие места — сама сеть и её протоколы. Ошибки в понимании того, как устроен стек и как работает IPv6, легко сводят на нет все усилия по оптимизации. Если хотите прокачаться глубже и убрать эти «слепые зоны», приходите на бесплатные практические занятия:
-
10 сентября в 20:00 — Почему протоколы маршрутизации работают не только на сетевом уровне
-
24 сентября в 20:00 — IPv6: всё, что нужно знать сетевому инженеру
Освоить актуальные протоколы маршрутизации и научиться предотвращать и устранять проблемы, возникающие в сетях, можно на курсе «Network Engineer. Professional». Пройдите вступительный тест, чтобы проверить свой уровень знаний.
Чтобы оставаться в курсе актуальных технологий и трендов, подписывайтесь на Telegram-канал OTUS.
ссылка на оригинал статьи https://habr.com/ru/articles/940670/
Добавить комментарий