Настройка HTTP-кеширования: Cache-Control и ETag
Правильные заголовки кеширования позволяют браузерам и CDN хранить ресурсы без повторных запросов к серверу. Статические ассеты (JS, CSS, шрифты) с правильным кешем загружаются мгновенно при повторном визите.
Cache-Control директивы
Cache-Control: public, max-age=31536000, immutable
| Директива | Значение |
|---|---|
public |
Кешировать в браузере и на прокси/CDN |
private |
Только в браузере (не на CDN) |
no-cache |
Всегда проверять актуальность через сервер |
no-store |
Никогда не кешировать |
max-age=N |
Кешировать N секунд |
s-maxage=N |
Для CDN (переопределяет max-age) |
immutable |
Файл не изменится — не проверять даже при F5 |
must-revalidate |
После max-age — обязательно проверить |
Стратегия по типам ресурсов
# Nginx конфигурация
# Статические ассеты с хешем в имени (Vite, Webpack)
# app.a1b2c3d4.js — хеш меняется при изменении файла
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Vary Accept-Encoding;
}
# Шрифты — также immutable (меняем URL при обновлении)
location ~* \.(woff2|woff|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin *;
}
# Изображения — долгий кеш, но без immutable
location ~* \.(webp|avif|jpg|jpeg|png|gif|svg|ico)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# HTML — короткий кеш или no-cache
location ~* \.html$ {
expires 5m;
add_header Cache-Control "public, max-age=300, must-revalidate";
}
# API — не кешировать (или кешировать с s-maxage для CDN)
location /api/ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma no-cache;
}
ETag — условные запросы
ETag позволяет проверить актуальность кеша без передачи всего файла:
# Первый запрос
GET /images/logo.png
Response: 200 + ETag: "abc123" + Last-Modified: Mon, 15 Mar 2024
# Повторный запрос
GET /images/logo.png
If-None-Match: "abc123"
Response: 304 Not Modified (тело не передаётся — только заголовки)
# Nginx: ETag включён по умолчанию
etag on;
# Last-Modified тоже включён по умолчанию
Laravel: кеш для API-ответов
// Кеш на уровне HTTP-ответа для API
public function products(Request $request): JsonResponse
{
$products = Cache::remember('api:products', 300, fn() =>
Product::with('category')->where('is_active', true)->get()
);
$etag = md5($products->pluck('updated_at')->max());
if ($request->header('If-None-Match') === $etag) {
return response()->noContent(304);
}
return response()->json($products)
->header('Cache-Control', 'public, max-age=300')
->header('ETag', $etag);
}
Инвалидация кеша через версионирование файлов
Vite автоматически добавляет хеш к именам файлов при сборке:
app.js → app.a1b2c3d4.js
app.css → app.9f8e7d6c.css
Изменение кода → новый хеш → браузер загружает новый файл, невзирая на immutable.
Vary: правильное кеширование с переговорами контента
# Разные кеши для gzip/brotli версий
location ~* \.(js|css)$ {
add_header Vary Accept-Encoding;
gzip_static on;
brotli_static on;
}
Без Vary: Accept-Encoding CDN может отдать gzip-версию браузеру, не поддерживающему gzip.
Stale-While-Revalidate
# Отдавать кешированный ответ, обновлять в фоне
add_header Cache-Control "public, max-age=300, stale-while-revalidate=3600";
Пользователь получает мгновенный ответ из кеша; в фоне браузер запрашивает свежую версию к следующему визиту.
Срок настройки: несколько часов для полной настройки Nginx.







