// статьи / статья
#статья #кэш#javascript#браузер#отладка

> Кэш — главный враг: почему сайт показывает старое

Версии компонентов, «грязный» браузер и почему Playwright в чистом контексте ничего не ловит.

SkilinPur 25.10.2026 5 мин чтения серия: как это устроено 👁
Кэш — главный враг: почему сайт показывает старое

Ты поправил одну ссылку в футере, обновил страницу — и видишь старую. Или новую только на главной. Или не видишь вообще. Это не магия. Это кэш.

Кэш — это не баг, а слоёный пирог

Когда пользователь открывает сайт, между ним и базой есть несколько слоёв:

  • браузерный кэш;
  • кэш CDN, если он есть;
  • Caddy;
  • PHP;
  • JSON-файлы;
  • JS, который эти JSON читает.

Каждый слой может отдать старое. И каждый надо версионировать отдельно.

Публичная часть в основном статическая, поэтому кэш — не «оптимизация на потом», а часть архитектуры. Если не управлять им, он управляет тобой.

?v= — самая простая привычка

Для CSS и JS я добавляю версию в URL:

<link rel="stylesheet" href="/css/main.css?v=14">
<script src="/js/main.js?v=5.3"></script>

Меняешь файл — поднимаешь версию в HTML. Это не «красиво», зато браузер гарантированно скачает новый файл.

Грабля начинается, когда версия есть только у одного файла. Например, поправил components/header.html и поднял header.html?v=2, но забыл, что компонент вставляет page-init.js. Браузер берёт старый page-init.js, тот запрашивает старый URL компонента — и пользователь видит старую шапку.

Поэтому правило: бампай и версию компонента, и версию JS, который его грузит, и версию этого JS в HTML. Три уровня, не один.

JSON: ?t=Date.now()

JSON-файлы меняются часто: статьи, проекты, команда. Если браузер закэширует data/articles.json, новая статья не появится, пока не истечёт TTL.

Решение простое:

fetch('/data/articles.json?t=' + Date.now())

И заголовок Cache-Control: no-cache на стороне Caddy. no-cache не значит «не кэшировать вообще» — он значит «проверь, не изменилось ли». Для небольших JSON это нормально.

Минус: каждый запрос идёт на сервер. Плюс: данные всегда свежие. Для портала с небольшим трафиком плюс важнее.

Почему Playwright в чистом контексте ничего не ловит

Это отдельная боль.

Пишешь тест: открыть страницу, проверить, что ссылка есть. Playwright запускает чистый браузер, без кэша. Всё зелёное. Открываешь сайт в своём браузере — ссылки нет.

Потому что у тебя в браузере уже лежит старый page-init.js, который грузит старый footer.html. Playwright этого не видит: у него кэша нет.

Урок: тест в чистом контексте проверяет код, а не пользователя. Пользователь приходит с кэшем.

Что делать:

  • проверять в обычном браузере с отключённым кэшем;
  • проверять в persistent context Playwright;
  • делать hard reload (Ctrl+Shift+R);
  • смотреть Network на предмет from disk cache;
  • сравнивать версии в HTML и в загруженных файлах.

Иногда полезно открыть сайт в двух браузерах: чистом и «грязном». Если поведение разное — почти наверняка кэш.

Где мы наступили на грабли

Ссылка «видна только на главной»

Классика. Поправил футер, бампнул footer.html?v=, но не бампнул page-init.js в HTML. На главной футер грузится одним путём, на подстраницах — другим. В итоге на главной новая ссылка есть, на остальных — нет.

Лечение: бампать всю цепочку. И записать это в проектную память, чтобы не повторять.

Статья и аватар автора

После добавления аватара в статью страница продолжала показывать старый вариант. Пришлось добавить visitors.css, no-store и версию article.css?v=3. Кэш держал CSS, а no-store на HTML не помогал, потому что стили грузились отдельно.

Урок: no-store на HTML не отменяет кэш CSS и JS. Версионировать надо всё.

Cookie-баннер

Поправил текст в footer.html, но баннер остался старым. Потому что баннер — часть футера, а футер грузится JS. Пришлось бампать и компонент, и page-init.js, и main.js.

Теперь перед правкой футера я смотрю, какие скрипты его грузят.

Кэш и админка

В админке кэш тоже есть, но другой:

  • браузер кэширует admin.css и admin.js;
  • страницы с формами нельзя кэшировать;
  • после сохранения делается redirect, чтобы не отправить форму повторно.

Если админка показывает старые данные после сохранения — это не всегда кэш. Иногда это база, иногда — экспорт, иногда — то, что страница не перезапросила JSON.

Урок: «почисти кэш» — не универсальный ответ. Надо понять, какой слой отдаёт старое.

Стратегия версий

У меня нет идеальной системы. Есть набор правил:

  1. HTML — не кэшировать надолго, особенно с пользовательскими данными.
  2. CSS/JS — версия в URL, длинный кэш.
  3. JSON — ?t= и no-cache.
  4. Картинки — имя файла с датой или хэшем.
  5. Компоненты — версия в URL и версия загрузчика.
  6. Статьи — no-store, потому что контент и авторские данные.

Это не rocket science. Но без этого сайт начинает «показывать старое» в самый неподходящий момент.

Как отлаживать

Мой порядок:

  1. Открыть DevTools → Network → Disable cache.
  2. Проверить, какой файл грузится и с каким статусом.
  3. Посмотреть, совпадает ли версия в HTML с версией в URL.
  4. Проверить curl -I для заголовков.
  5. Сравнить с чистым профилем браузера.
  6. Если всё равно странно — проверить, не отдаёт ли Caddy статику из другого каталога.

Иногда проблема вообще не в кэше: например, JSON не пересобрался после изменения в админке. Тогда «почисти кэш» только маскирует баг.

Вместо вывода

Кэш — это распределённая ошибка, которая ждёт своего часа. Он полезен, но требует дисциплины.

Самое простое правило, которое спасает чаще всего: если файл изменился — у него должна измениться версия. Для CSS, JS, компонентов и JSON. Иначе пользователь увидит старый мир, а ты будешь искать баг там, где его нет.

// реакции
SkilinPurучастник InIProject