Back/forward cache

Когда пользователь нажимает «назад» или «вперёд», браузер может не загружать страницу заново, а достать из памяти полный снимок: DOM, JS-кучу, состояние скролла — всё. Страница замораживается при уходе и размораживается при возврате. Переход ощущается мгновенно, потому что это не навигация в привычном смысле — это restore.

Браузер делает эту оптимизацию автоматически, но страница должна соответствовать определённым условиям. Она не попадёт в bfcache, если:

  • есть listener на unload
  • открыт WebSocket
  • на документе стоит Cache-Control: no-store
  • есть незавершённая IndexedDB транзакция
  • и другие причины

Полный список можно посмотреть на MDN.

Диагностировать всё это можно прямо в DevTools: вкладка Application → Back/forward cache. Там можно протестировать, попадает ли страница в bfcache, и если нет — увидеть конкретный список блокеров.

Для продакшена есть программный способ — PerformanceNavigationTiming.notRestoredReasons. Через него можно собирать данные об использовании bfcache в RUM-метриках и понимать, что ломает кэш у реальных пользователей.

По итогу bfcache — один из самых «дешёвых» способов ускорить воспринимаемую производительность. Ничего не нужно дополнительно оптимизировать — достаточно не ломать нативное поведение браузера.

stale-while-revalidate

Ещё одна небольшая оптимизация в браузере — заголовок stale-while-revalidate.

Полностью он используется так:

Cache-Control: max-age=600, stale-while-revalidate=30

Что здесь происходит:

  • 0–600 сек — ресурс свежий, отдаётся из кэша
  • 600–630 сек — ресурс устарел, но браузер всё равно отдаёт его мгновенно, а в фоне идёт за новой версией
  • после 630 сек — кэш полностью стух, пользователь ждёт

Ключевой момент: пользователь, попавший в stale-окно, видит старую версию. Фоновый запрос незаметно обновляет кэш, и уже в следующий раз посетитель получит свежие данные.

Где его используем, а где нет?

  • Нехешированная статика (/logo.png, /fonts/custom.woff2) — используем, потому что URL не меняется при обновлении файла.
  • API-ответы и HTML, которые меняются нечасто (каталог, лендинг, результаты поиска) — тоже используем, это даёт мгновенный ответ, а данные отстают максимум на пару минут.
  • Хешированная статика (main.a3f8c2.js) — не имеет смысла использовать stale-while-revalidate, тут правильнее будет immutable, потому что файл по этому URL с хэшом не изменится никогда.
  • Критичные данные (баланс, цена, статус заказа) — тут уже нужен no-cache.

И да, паттерн SWR знаком многим по React-библиотекам (swr, React Query), но лично я раньше не задумывалась о том, что это буквально тот же принцип, только взятый из HTTP.

Заметка собрана из постов в Telegram-канале @startpoint_dev: первый, второй.