Ты поправил одну ссылку в футере, обновил страницу — и видишь старую. Или новую только на главной. Или не видишь вообще. Это не магия. Это кэш.
Кэш — это не баг, а слоёный пирог
Когда пользователь открывает сайт, между ним и базой есть несколько слоёв:
- браузерный кэш;
- кэш 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.
Урок: «почисти кэш» — не универсальный ответ. Надо понять, какой слой отдаёт старое.
Стратегия версий
У меня нет идеальной системы. Есть набор правил:
- HTML — не кэшировать надолго, особенно с пользовательскими данными.
- CSS/JS — версия в URL, длинный кэш.
- JSON —
?t=иno-cache. - Картинки — имя файла с датой или хэшем.
- Компоненты — версия в URL и версия загрузчика.
- Статьи —
no-store, потому что контент и авторские данные.
Это не rocket science. Но без этого сайт начинает «показывать старое» в самый неподходящий момент.
Как отлаживать
Мой порядок:
- Открыть DevTools → Network → Disable cache.
- Проверить, какой файл грузится и с каким статусом.
- Посмотреть, совпадает ли версия в HTML с версией в URL.
- Проверить
curl -Iдля заголовков. - Сравнить с чистым профилем браузера.
- Если всё равно странно — проверить, не отдаёт ли Caddy статику из другого каталога.
Иногда проблема вообще не в кэше: например, JSON не пересобрался после изменения в админке. Тогда «почисти кэш» только маскирует баг.
Вместо вывода
Кэш — это распределённая ошибка, которая ждёт своего часа. Он полезен, но требует дисциплины.
Самое простое правило, которое спасает чаще всего: если файл изменился — у него должна измениться версия. Для CSS, JS, компонентов и JSON. Иначе пользователь увидит старый мир, а ты будешь искать баг там, где его нет.
