Cache-Control
Кэш нужен не “для скорости вообще”, а для предсказуемого поведения сайта после обновлений.
У статического сайта обычно есть два разных типа файлов:
- HTML — должен быстро обновляться после деплоя.
- Хэшированные assets — CSS/JS с именами, которые меняются при изменении содержимого. Их можно кэшировать надолго.
Если эти правила перепутать, появятся странные проблемы: страница старая, стили новые; или наоборот — HTML обновился, а браузер держит старый JS.
Хорошая базовая схема
| Что | Пример | Cache-Control |
|---|---|---|
| HTML | /, /ru/docs/ | public, must-revalidate |
| Assets | /assets/app.ABC123.js | public, max-age=31536000, immutable |
| 404 | /no-such-page | public, must-revalidate |
| Sitemap | /sitemap-index.xml | public, must-revalidate |
| robots.txt | /robots.txt | public, must-revalidate |
Почему HTML не надо кэшировать надолго
HTML — это точка входа. Именно он ссылается на актуальные CSS/JS-файлы.
Если поставить HTML:
Cache-Control: public, max-age=31536000, immutable
то браузер может долго не увидеть новую версию страницы после деплоя.
Для HTML лучше:
Cache-Control: public, must-revalidate
Это разрешает хранить файл, но требует перепроверять его актуальность.
Почему assets можно кэшировать надолго
Astro собирает CSS/JS в файлы с хэшами в имени. Пример:
/assets/cache-control.B1dARQ-M.css
/assets/hoisted.C4vOHhWK.js
Если содержимое меняется, имя файла тоже меняется. Поэтому старый asset можно безопасно держать в кэше долго.
Для assets подходит:
Cache-Control: public, max-age=31536000, immutable
immutable означает: “пока URL тот же, файл не изменится”.
Пример Nginx
Для assets:
location /assets/ {
try_files $uri =404;
access_log off;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header X-Content-Type-Options nosniff always;
}
Для HTML и обычных путей:
location / {
try_files $uri $uri/ =404;
add_header Cache-Control "public, must-revalidate" always;
add_header X-Content-Type-Options nosniff always;
}
Для 404:
error_page 404 /404.html;
location = /404.html {
internal;
add_header Cache-Control "public, must-revalidate" always;
}
Проверка HTML
curl -kI https://getsrv.app/
Ожидаемо:
HTTP/2 200
content-type: text/html
cache-control: public, must-revalidate
Проверка assets
ASSET="$(find /var/www/getsrv.app/assets -maxdepth 1 -type f | head -n 1 | sed 's#^/var/www/getsrv.app##')"
curl -kI "https://getsrv.app$ASSET"
Ожидаемо:
HTTP/2 200
cache-control: public, max-age=31536000, immutable
Проверка 404
curl -kI https://getsrv.app/no-such-page
Ожидаемо:
HTTP/2 404
cache-control: public, must-revalidate
Проверка sitemap
curl -kI https://getsrv.app/sitemap-index.xml
Ожидаемо:
HTTP/2 200
content-type: text/xml
cache-control: public, must-revalidate
Частые ошибки
Ошибка 1. expires и add_header Cache-Control вместе
В Nginx expires сам добавляет Cache-Control. Если рядом ещё вручную добавить Cache-Control, можно получить два заголовка.
Плохо:
expires 1h;
add_header Cache-Control "public, must-revalidate" always;
Лучше выбрать один способ. Для точного контроля проще использовать только add_header Cache-Control.
Ошибка 2. immutable для HTML
Плохо:
location / {
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
Так браузер может долго держать старый HTML.
Ошибка 3. Assets не в /assets/
Если сборщик складывает файлы в один путь, а Nginx кэширует другой путь, правила не сработают.
Проверьте Astro config:
build: {
assets: 'assets'
}
И Nginx:
location /assets/ {
...
}
Ошибка 4. Старый HTML ссылается на удалённый JS
Если используется rsync --delete, старые assets удаляются. Это правильно, но при слишком долгом HTML-cache браузер может открыть старый HTML, который ссылается на уже удалённый JS.
Поэтому HTML — must-revalidate, assets — immutable.
Короткая проверка после деплоя
curl -kI https://getsrv.app/ | grep -i cache-control
ASSET="$(find /var/www/getsrv.app/assets -maxdepth 1 -type f | head -n 1 | sed 's#^/var/www/getsrv.app##')"
curl -kI "https://getsrv.app$ASSET" | grep -i cache-control
curl -kI https://getsrv.app/no-such-page | grep -i cache-control
Ожидаемый смысл:
HTML: public, must-revalidate
Asset: public, max-age=31536000, immutable
404: public, must-revalidate