Кеширование — Cache-Control

Cache-Control — HTTP-заголовок, управляющий кешированием ресурсов в браузере и CDN: определяет срок хранения, условия инвалидации и доступность кеша для shared/private пользователей.

Зачем нужно

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

Где используется

  • Статические ресурсы (JS, CSS, шрифты, изображения) — долгосрочное кеширование
  • HTML-страницы — no-cache или короткий TTL
  • API-ответы — no-store для приватных данных или s-maxage для CDN
  • CDN-конфигурация (Cloudflare, Fastly, AWS CloudFront)

Основной контент

Директивы Cache-Control

# Для статики с cache-busting (хэш в имени файла)
Cache-Control: public, max-age=31536000, immutable
# public    → можно кешировать в CDN
# max-age   → 1 год в секундах
# immutable → браузер не проверяет обновления (используй с хэшами!)

# Для HTML страниц
Cache-Control: no-cache
# no-cache → кешировать, но проверять актуальность (ETag/Last-Modified)
# НЕ "не кешировать"! Это no-store

# Для приватных данных (API с сессией)
Cache-Control: private, no-store

# CDN кеш отдельно от браузера
Cache-Control: public, max-age=3600, s-maxage=86400
# max-age=3600    → браузер: 1 час
# s-maxage=86400  → CDN: 24 часа

Стратегии кеширования

1. Cache-busting (рекомендуется для JS/CSS)
   /bundle.a3f8c2d.js → Cache-Control: max-age=31536000, immutable
   При изменении → новый хэш → новый URL → новый кеш

2. Stale-While-Revalidate (для API)
   Cache-Control: max-age=60, stale-while-revalidate=600
   → Отдаёт кеш сразу, обновляет в фоне

3. ETag + If-None-Match (для HTML)
   ETag: "abc123"
   → Клиент: If-None-Match: "abc123"
   → Сервер: 304 Not Modified (тело не передаётся)

Express: настройка заголовков

const path = require('path');

// Статические файлы с хэшами → долгосрочный кеш
app.use('/static', express.static('dist', {
  maxAge: '1 year',
  immutable: true,
  // Устанавливает: Cache-Control: public, max-age=31536000, immutable
}));

// HTML → no-cache
app.get('*', (req, res) => {
  res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
  res.sendFile(path.join(__dirname, 'dist/index.html'));
});

// API: приватные данные
app.get('/api/profile', authenticate, (req, res) => {
  res.set('Cache-Control', 'private, no-store');
  res.json(req.user);
});

// API: публичные данные с CDN кешом
app.get('/api/products', (req, res) => {
  res.set('Cache-Control', 'public, max-age=60, s-maxage=3600, stale-while-revalidate=86400');
  res.json(products);
});

Service Worker: Cache API

// sw.js — кеш стратегия Cache First для статики
self.addEventListener('fetch', event => {
  if (event.request.destination === 'script' || event.request.destination === 'style') {
    event.respondWith(
      caches.match(event.request).then(cached => {
        return cached || fetch(event.request).then(response => {
          const clone = response.clone();
          caches.open('v1').then(cache => cache.put(event.request, clone));
          return response;
        });
      })
    );
  }
});

Частые ошибки

  • no-cache вместо no-store для чувствительных данных — no-cache кеширует, но проверяет
  • max-age=31536000 без хэша в имени файла — пользователи не получают обновления год
  • Кеширование персонализированных ответов с public — CDN отдаёт чужие данные
  • Отсутствие Vary: Accept-Encoding при gzip/brotli — CDN кеширует некорректно

Связанные темы

Ресурсы