Cache-Control — HTTP-заголовок, который определяет правила кэширования ресурсов на стороне клиента, прокси-серверов и CDN с помощью набора директив. В отличие от устаревшего заголовка Expires, Cache-Control поддерживает десятки комбинаций: max-age задаёт время жизни в секундах, private и public управляют доступностью кэша, no-cache и no-store — принудительной проверкой. По данным Google Web Dev (2025), правильная конфигурация Cache-Control может сократить время загрузки страниц на 50-80% для повторных посещений. Это делает заголовок критически важным для производительности веб- и мобильных приложений.
Главное
Cache-Control — это HTTP-заголовок, стандартизированный в HTTP/1.1 (RFC 7234), который позволяет серверу указать, как и в течение какого времени клиенты, прокси и CDN могут кэшировать ответ. В отличие от Expires (HTTP/1.0), Cache-Control использует директивы — текстовые команды, которые комбинируются через запятую: Cache-Control: public, max-age=3600, must-revalidate. Заголовок даёт тонкий контроль над каждым звеном цепочки кэширования.
Кэширование — один из фундаментальных механизмов производительности веба и мобильных приложений. Без него каждый запрос пользователя шёл бы напрямую к серверу, вызывая избыточную нагрузку и задержки. Cache-Control определяет три уровня кэширования: браузер/приложение (private cache), прокси-серверы (shared cache) и CDN (distributed cache). Каждый уровень интерпретирует директивы по-своему.
Неправильная настройка Cache-Control — одна из самых частых причин проблем с производительностью. Слишком агрессивное кэширование приводит к тому, что пользователи видят устаревшие данные. Слишком слабое — к избыточным запросам к серверу и медленной загрузке. По данным Akamai (2025), оптимизация Cache-Control для статического контента снижает нагрузку на сервер на 70-90% и улучшает время загрузки на 40-60% для мобильных пользователей.
Cache-Control появился в HTTP/1.1 (RFC 2616, 1999) как замена Expires. Expires имел фундаментальную проблему: он использовал абсолютную дату, которая зависела от часового пояса сервера и клиента. Cache-Control решил эту проблему, перейдя на относительное время (max-age в секундах от момента получения ответа). Позже в RFC 7234 (2014) были добавлены новые директивы: immutable для статики, stale-while-revalidate и stale-if-error для отложенной проверки.
Cache-Control включает более 10 директив, разделённых на три группы: директивы запроса (клиент → сервер), директивы ответа (сервер → клиент) и расширения. На практике в мобильной разработке используются 6-7 основных директив ответа, покрывающих 95% сценариев кэширования. Рассмотрим каждую с примерами и рекомендациями.
| Директива | Значение | Пример |
|---|---|---|
| max-age | Время жизни в секундах с момента ответа | max-age=3600 — 1 час |
| s-maxage | max-age для shared cache (прокси, CDN) | s-maxage=86400 — 1 день для CDN |
| public | Разрешает кэширование всем (включая прокси) | public, max-age=3600 |
| private | Разрешает кэш только браузеру/приложению | private, max-age=600 |
| no-cache | Не использовать без проверки (304 обязателен) | no-cache |
| no-store | Запрет кэширования полностью | no-store |
| must-revalidate | После max-age обязательно проверять у origin | max-age=3600, must-revalidate |
| immutable | Ресурс не изменится (для версионированной статики) | max-age=31536000, immutable |
max-age — самая важная директива. Она запрещает клиенту делать запрос к серверу в течение указанного времени. Для статики (CSS, JS, изображения) max-age обычно устанавливают от 1 дня до 1 года. Для API-ответов — от 0 секунд (данные всегда свежие) до 5-10 минут (справочные данные). s-maxage позволяет задать разное время жизни для CDN и браузера: CDN хранит копию 1 день, браузер — 1 час.
Эти две директивы часто путают. no-cache не запрещает кэширование — он требует проверять закэшированную копию при каждом использовании через условный запрос (If-Modified-Since или If-None-Match). Если сервер отвечает 304 — клиент использует кэш. Если 200 — обновляет. no-store же полностью запрещает сохранять ответ в любом кэше, включая дисковый и оперативный. Используйте no-store только для чувствительных данных — токены, платёжные данные, персональные документы.
Заголовок Expires (HTTP/1.0) также указывает время жизни ресурса, но использует абсолютную дату: Expires: Thu, 03 Jul 2026 12:00:00 GMT. Cache-Control max-age — относительное время от момента ответа. Разница критична для распределённых систем: если сервер и клиент находятся в разных часовых поясах, Expires может быть интерпретирован неверно. Cache-Control не имеет этой проблемы — 3600 секунд всегда 3600 секунд.
Когда оба заголовка присутствуют, Cache-Control имеет приоритет над Expires. Это определено в RFC 7234: «Если ответ содержит Cache-Control с директивой max-age, получатель ДОЛЖЕН игнорировать Expires». На практике рекомендуется не возвращать Expires вообще для современных клиентов, так как Cache-Control покрывает все сценарии Expires. Однако для обратной совместимости со старыми прокси и браузерами можно возвращать оба заголовка.
Expires сохранился в основном для статического контента на Nginx и Apache — эти серверы автоматически добавляют оба заголовка. Если в вашем проекте встречается Expires без Cache-Control, замените его на Cache-Control с max-age: точность управления кэшем повышается, а зависимость от часового пояса устраняется. Для миграции достаточно настроить сервер на добавление Cache-Control вместо Expires.
# Nginx: Cache-Control для статических файлов
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable, max-age=2592000";
}
# Разные политики для разных типов контента
location /api/config {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
location /api/static-data {
expires 5m;
add_header Cache-Control "public, max-age=300";
}
В конфигурации Nginx статическим файлам (CSS, JS, изображения) устанавливается Cache-Control на 30 дней с атрибутом immutable — этот атрибут сообщает браузеру, что ресурс никогда не меняется по этому URL (версионирование через хеш в имени файла). API-эндпоинты используют no-cache для динамических данных и public с коротким max-age для справочных — часто запрашиваемые и редко меняющиеся списки.
В мобильных приложениях Cache-Control играет особую роль из-за ограничений мобильных сетей: высокая задержка, нестабильное соединение, лимиты трафика. Правильное кэширование позволяет показывать пользователю данные мгновенно, даже офлайн, и обновлять их в фоне. OkHttp на Android и URLSession на iOS имеют встроенные системы кэширования, учитывающие Cache-Control.
OkHttp использует CacheInterceptor, который читает Cache-Control из ответа и автоматически управляет кэшированием. Если сервер вернул Cache-Control: max-age=3600, OkHttp не будет делать запрос к серверу в течение часа. После истечения max-age OkHttp отправляет условный запрос с If-Modified-Since и If-None-Match. Настройка кэша в OkHttp: OkHttpClient.Builder().cache(Cache(directory, maxSize)).
fun createCachedClient(cacheDir: File): OkHttpClient {
return OkHttpClient.Builder()
.cache(Cache(cacheDir, 10L * 1024 * 1024))
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control",
"public, max-age=300")
.removeHeader("Pragma")
.build()
}
.build()
}
Код создаёт OkHttpClient с кэшем на 10 МБ и переопределяет Cache-Control через NetworkInterceptor. Если сервер не возвращает Cache-Control или использует Expires, интерцептор добавляет public, max-age=300 (5 минут). interceptor удаляет устаревший заголовок Pragma (HTTP/1.0) для совместимости. По аналогичной схеме работает кэширование на iOS через URLCache.shared с настройкой memoryCapacity и diskCapacity.
Директива stale-while-revalidate позволяет показывать пользователю устаревший кэш (stale), пока приложение в фоне загружает свежие данные. Это даёт эффект мгновенного отклика: пользователь видит контент сразу, а через секунду он обновляется до актуального. Поддерживается OkHttp начиная с версии 3.10 и URLCache на iOS 14+. Пример: Cache-Control: max-age=3600, stale-while-revalidate=300 — 1 час актуального кэша, потом 5 минут показа устаревшего с фоновым обновлением.
Разные типы ресурсов требуют разных стратегий кэширования. Рассмотрим оптимальные конфигурации для типовых сценариев в мобильной разработке. Для статического контента с хешем в имени файла (bundle.abc123.js) можно устанавливать max-age до 1 года с immutable. Для API-списков, которые обновляются редко (справочники, категории), — max-age от 5 минут до 1 часа с stale-while-revalidate.
| Тип ресурса | Cache-Control | Пояснение |
|---|---|---|
| Версионированная статика | public, max-age=31536000, immutable | 1 год, файлы не меняются (хеш в URL) |
| Неверсионированная статика | public, max-age=86400, must-revalidate | 1 день с принудительной проверкой после |
| API: справочные данные | public, max-age=600, stale-while-revalidate=60 | 10 минут кэш + 1 минута stale |
| API: пользовательские данные | private, max-age=60 | 1 минута, только для конкретного пользователя |
| API: чувствительные данные | no-store | Полный запрет кэширования |
| HTML-страницы | no-cache, must-revalidate | Проверка при каждом запросе, 304 при неизменности |
Важно помнить о безопасности: для ответов, содержащих персональные данные пользователя, всегда устанавливайте private. Без этой директивы публичный прокси (например, корпоративный) может закэшировать ответ и отдать его другому пользователю. Для токенов аутентификации и платёжной информации используйте no-store — даже private кэш не должен сохранять эти данные на диск.
Для проверки корректности Cache-Control используйте заголовок Age (сколько секунд кэш хранится) и X-Cache (hit/miss на CDN). В браузере — вкладка Network, колонка Size показывает «from disk cache» или «304 Not Modified». Если ресурс должен кэшироваться, но загружается каждый раз — проверьте, не добавляет ли сервер Cache-Control: no-cache или Pragma: no-cache вместе с вашими директивами.
Часто задаваемые вопросы
max-age действует для всех кэшей (включая браузеры), s-maxage — только для shared cache (прокси, CDN). Если указан s-maxage, CDN игнорирует max-age и использует s-maxage. Это позволяет задать разное время жизни для браузера и CDN.
Нет, после отправки ответа с max-age клиент не будет делать запрос до истечения таймера. Для немедленной инвалидации кэша нужно изменить URL ресурса (добавить версию/хеш) и разослать push-уведомления или WebSocket-сообщения для принудительного сброса.
Директива immutable (RFC 8246) сообщает браузеру, что ресурс никогда не изменится по данному URL. Браузер даже не пытается сделать условный запрос при обновлении страницы — использует кэш до истечения max-age. Работает только с версионированными файлами.
Googlebot учитывает Cache-Control: долгое кэширование ускоряет повторное сканирование. noindex с fast-кэшем — ок. no-store может замедлить индексацию, так как Googlebot будет загружать страницу каждый раз с нуля. Слишком короткий max-age увеличивает нагрузку на сервер при сканировании.
Через helmet или middleware: res.set('Cache-Control', 'public, max-age=3600'). Для статики используйте express.static с параметром maxAge: express.static('public', {maxAge: '1y'}). Для динамических маршрутов — индивидуально в каждом обработчике.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также