Доступен — Локальные цифровые продукты
HCA · Studio
TR — Контакты ↗
Заметка 11 · Развёртывание

Вы развернули, а посетитель видит старую страницу

Файл на сервере актуальный, в браузере — старый. Жёсткое обновление помогает, но в приватном окне снова старая страница. Проблема не в кэше браузера, а в слое между вами — и решается она не кнопкой очистки при каждом деплое.

Слой
Cloudflare
Симптом
Устаревший HTML
Проверка
cf-cache-status
Короткий ответ

Cloudflare по умолчанию кэширует статические ассеты (CSS, JS, изображения, шрифты) и не кэширует HTML. Если ваш HTML всё же кэшируется, значит либо правило говорит кэшировать всё, либо сервер отдаёт для HTML длинный Cache-Control. Правильное решение — разделить два слоя: версионируйте статические ассеты в имени файла или строке запроса, чтобы очистка вообще не требовалась, ведь новая версия — это новый адрес. HTML держите короткоживущим и после деплоя чистите только изменившиеся адреса. Для диагностики читайте заголовок cf-cache-status: HIT — из кэша, MISS — с сервера, DYNAMIC — не кэшируется вовсе.

Сначала диагноз

У жалобы «показывается старая страница» есть минимум три причины: кэш браузера, кэш CDN или собственный кэш сервера. Вместо догадок спросите заголовки:

curl -sSI https://example.com/ | grep -iE 'cf-cache-status|cache-control|age|last-modified'

Что читать:

  • cf-cache-status: HIT — ответ пришёл из кэша CDN, до вашего сервера дело не дошло
  • cf-cache-status: MISS — в кэше не было, взято с сервера
  • cf-cache-status: DYNAMIC — этот адрес не кэшируется вовсе
  • age: 8412 — сколько секунд ответ лежит в кэше

HIT с большим age на HTML означает, что причина найдена. DYNAMIC означает, что CDN ни при чём и смотреть надо на сервер.

Два разных слоя

На статическом сайте есть два типа содержимого, и их потребности в кэшировании противоположны.

Ассеты (CSS, JS, изображения, шрифты) меняются редко, а когда меняются — меняются целиком. Им следует оставаться в кэше как можно дольше.

HTML меняется часто и должен становиться видимым сразу. Его либо не кэшируют вовсе, либо держат очень недолго.

Большинство проблем развёртывания возникает от применения одной политики к обоим. «Кэшировать всё» делает сайт быстрым, а каждое обновление невидимым; «не кэшировать ничего» лишает CDN смысла.

Версионируйте ассеты

Для ассетов правильный ответ — не очистка, а смена адреса. Если адрес меняется вместе с содержимым, старая копия в кэше перестаёт иметь значение: её никто не запрашивает.

<link rel="stylesheet" href="/styles.min.css?v=20260918" />
<script src="/script.min.js?v=20260918" defer></script>

Ещё надёжнее выводить строку версии из хеша содержимого файла — тогда забыть её обновить становится невозможно. Каким бы методом вы ни пользовались, правило одно: содержимое ассета не должно меняться без смены его адреса.

Как только это соблюдается, годовой срок кэширования ассетов полностью безопасен, и очистка после деплоя для них не нужна.

Единственное слабое место такой схемы — забытое обновление версии. Обновите CSS, не сменив версию, и посетители получат новый HTML со старыми стилями, то есть сломанную вёрстку — и обычная очистка кэша это не исправит, потому что файл отдаёт собственный кэш браузера.

Сторона HTML

Для HTML есть два разумных варианта. Первый — не кэшировать вовсе, оставив CDN слоем терминации TLS и защиты. Второй — короткий срок жизни, несколько минут, вместе с перепроверкой.

На стороне сервера короткий срок выглядит так:

Cache-Control: public, max-age=300, must-revalidate

Этот заголовок говорит одно и то же и браузеру, и CDN, и не позднее чем через пять минут после деплоя все видят новое содержимое. Если страницы уже отдают ETag и Last-Modified, проверка по истечении срока обходится дёшево: неизменившееся тело не скачивается заново.

Очистка после деплоя

При версионированных ассетах очистка нужна только для HTML — и должна быть последним шагом скрипта развёртывания, а не тем, что вы вспоминаете сделать руками.

# очистить только изменившиеся адреса
curl -sS -X POST \
  "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://example.com/","https://example.com/services/"]}'

Очистка всего (purge_everything) проста, но это грубый инструмент: из кэша вылетают и все ассеты, следующие посетители скачивают заново всё подряд, а ваш сервер получает лишнюю нагрузку. На маленьком сайте вы этого не заметите, на нагруженном — заметите.

После очистки проверьте результат — снова, не предполагайте:

curl -sSI https://example.com/ | grep -i cf-cache-status   # ожидаем MISS

Ловушки

Режим разработки. Режим разработки Cloudflare временно отключает кэширование и сам выключается через несколько часов. Полезен при отладке; оставленный включённым, становится частой причиной жалобы «сайт стал медленным».

Ответы 404 тоже кэшируются. Если страница была развёрнута не полностью и вернула 404, этот 404 попадает в кэш. Загрузить файл позже недостаточно — адрес продолжит отдавать 404, пока вы не очистите именно его.

Порядок имеет значение. Сначала загрузка, потом очистка. В обратном порядке CDN заново закэширует старое содержимое, и будет казаться, что ничего не изменилось.

Заголовки сервера управляют CDN. Если вы отдаёте для HTML длинный Cache-Control, браузеры будут держать файл у себя независимо от правил CDN — а очистка кэша CDN браузерный кэш не трогает.

Итог

Увидеть старое содержимое после деплоя — это не сбой кэша, а отсутствие политики. Версионируйте ассеты и кэшируйте их надолго; HTML держите короткоживущим и делайте точечную очистку последним шагом скрипта развёртывания. Как только эти два пункта на месте, весь класс ошибок «забыл почистить кэш» исчезает — а чтобы понять, что происходит, всегда достаточно одного заголовка: cf-cache-status.

Быстрая справка
Диагностика
cf-cache-status: HIT из кэша, MISS с сервера, DYNAMIC не кэшируется
По умолчанию
Cloudflare кэширует статические ассеты и обычно не кэширует HTML
Ассеты
Версионируйте (?v= или хеш содержимого) — очистка не нужна
HTML
max-age=300, must-revalidate либо не кэшировать вовсе
Очистка
Должна быть последним шагом скрипта деплоя, а не ручной привычкой
Точечно
Предпочитайте {"files":[...]}; purge_everything — грубый инструмент
Порядок
Сначала загрузка, потом очистка — иначе старое закэшируется снова
Забывают
Ответы 404 тоже кэшируются; после исправления очистите этот адрес
Частые вопросы

О Cloudflare и развёртывании.

Я загрузил файл, но сайт показывает старую версию. С чего начать?

Перестаньте гадать и прочитайте заголовки: curl -sSI и посмотрите cf-cache-status, age и cache-control. HIT с большим age означает, что ответ пришёл из кэша CDN. DYNAMIC означает, что CDN ни при чём и проблема на сервере или в браузере. Одна команда разделяет три возможности.

Плохо ли запускать purge_everything при каждом деплое?

Это работает, но инструмент грубый. Из кэша вылетают и все ассеты, поэтому следующие посетители заново скачивают всё, от CSS до изображений, а ваш сервер получает лишнюю нагрузку. Если ассеты версионированы, их вообще не нужно чистить; очищайте только изменившиеся адреса HTML.

Почему версионирование ассетов лучше очистки кэша?

Потому что оно убирает состояние гонки. Когда версионированный адрес меняется, новый файл — это новый адрес; старую копию в кэше никто не запрашивает, и очищать её не требуется. Именно это делает безопасным годовой срок кэширования и сводит развёртывание к одной переменной: не забыть поднять версию.

Я исправил страницу, но она всё ещё отдаёт 404.

Ответы 404 тоже кэшируются. Если не до конца развёрнутый адрес однажды вернул 404, загрузить файл позже недостаточно — нужно очистить именно этот адрес. Проверяйте после деплоя не только главную страницу, но и новые адреса.

Доступность и смета

Есть идея?
Можно даже одной фразой.