Как браузер понимает когда нужно что-либо закэшировать?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как браузер управляет кешированием ресурсов
Браузер использует HTTP заголовки кеша и встроенные эвристики для определения того, какие ресурсы и как долго кешировать. Это критически важно для производительности веба.
HTTP Cache Headers
Основной механизм кеширования — Cache-Control заголовок:
// На сервере (Node.js/Express пример)
app.get('/api/data', (req, res) => {
// Не кешировать — всегда свежие данные
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.json(data);
});
app.get('/images/logo.png', (req, res) => {
// Кешировать на 1 год (для static assets с version hash)
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
res.sendFile('logo.png');
});
app.get('/api/profile', (req, res) => {
// Кешировать на 5 минут, затем проверить валидность
res.setHeader('Cache-Control', 'private, max-age=300, must-revalidate');
res.json(profile);
});
Директивы Cache-Control
max-age — основной параметр, указывает время жизни в секундах:
// Cache-Control: max-age=3600
// Ресурс свежий 1 час (3600 сек)
// После истечения — браузер должен переспросить сервер
// Cache-Control: max-age=0
// Сразу устаревший, нужна перепроверка
public vs private:
// Cache-Control: public
// Могут кешировать браузер И промежуточные кеши (CDN, proxies)
res.setHeader('Cache-Control', 'public, max-age=3600');
// Cache-Control: private
// Кешируется только в браузере, не на промежуточных кешах
res.setHeader('Cache-Control', 'private, max-age=300');
no-cache vs no-store:
// no-cache — Кешируется, но перепроверяется всегда
// Браузер отправит If-Modified-Since или ETag
res.setHeader('Cache-Control', 'no-cache');
// no-store — Не кешировать вообще
// Ресурс каждый раз загружается с сервера
res.setHeader('Cache-Control', 'no-store');
immutable — Ресурс никогда не изменится:
// Используется для versioned assets
// /images/logo.abc123def456.png — hash в имени гарантирует уникальность
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
Механизм Conditional Requests
Когда max-age истекает, браузер проверяет, изменился ли ресурс:
// Сервер отправляет в первый раз:
res.setHeader('ETag', '"abc123"');
res.setHeader('Last-Modified', 'Mon, 01 Jan 2024 00:00:00 GMT');
res.setHeader('Cache-Control', 'max-age=3600');
// Браузер кеширует и ETag
// Через час отправляет условный запрос:
// If-None-Match: "abc123"
// If-Modified-Since: Mon, 01 Jan 2024 00:00:00 GMT
// Если сервер вернёт 304 Not Modified — кеш переиспользуется
if (req.headers['if-none-match'] === currentETag) {
res.status(304).end(); // Not Modified
} else {
res.status(200).json(data);
}
Expires заголовок (deprecated)
// Старый способ (все ещё поддерживается)
res.setHeader('Expires', 'Wed, 01 Apr 2026 12:00:00 GMT');
// Но Cache-Control имеет приоритет и рекомендуется
res.setHeader('Cache-Control', 'max-age=86400');
res.setHeader('Expires', 'Wed, 01 Apr 2026 12:00:00 GMT'); // Игнорируется
Service Workers и кеширование
Для более гибкого контроля используется Service Worker API:
// service-worker.js
self.addEventListener('fetch', (event) => {
const { request } = event;
// Кеширование "cache first" стратегия
event.respondWith(
caches.match(request).then((response) => {
if (response) return response;
return fetch(request).then((response) => {
if (!response || response.status !== 200) {
return response;
}
// Кешируем только успешные ответы
const responseToCache = response.clone();
caches.open('v1').then((cache) => {
cache.put(request, responseToCache);
});
return response;
});
}).catch(() => caches.match('/offline.html'))
);
});
Стратегии кеширования
Cache First — для static assets:
// Сначала ищем в кеше, если нет — запрашиваем сервер
if (cache.has(url)) return cache.get(url);
return fetch(url).then(response => {
cache.set(url, response);
return response;
});
Network First — для динамических данных:
// Сначала идём на сервер, если не доступен — кеш
return fetch(url)
.then(response => {
cache.set(url, response);
return response;
})
.catch(() => cache.get(url));
Stale While Revalidate — лучшее из обоих миров:
// Возвращаем кеш сразу, затем обновляем в фоне
const cached = cache.get(url);
if (cached) {
fetch(url).then(response => cache.set(url, response));
return cached;
}
return fetch(url).then(response => {
cache.set(url, response);
return response;
});
Эту стратегию можно выразить через заголовки:
res.setHeader('Cache-Control', 'public, max-age=300, stale-while-revalidate=86400');
// Свежий 5 минут, затем можно использовать устаревший до 1 дня
Инструменты разработчика
В DevTools -> Network tab видно:
Size: from disk cache (синий) — из памяти браузера
Size: from memory cache (зелёный) — из оперативной памяти
Size: 1.2 KB (красный) — загружено с сервера
200 (сервер) — свежий ответ
304 Not Modified — не изменился, кеш валидный
Best Practices
1. Версионируйте assets:
// Плохо
<script src="/app.js"></script>
// Всегда будет переузнать старый кеш
// Хорошо
<script src="/app.abc123def456.js"></script>
// С изменением кода меняется hash, новый файл загружается
2. Используйте стратегии в зависимости от типа:
// HTML — no-cache, всегда проверять
Cache-Control: no-cache
// CSS/JS — long-term с versioning
Cache-Control: public, max-age=31536000, immutable
// API responses — короткий кеш
Cache-Control: private, max-age=300, must-revalidate
3. Тестируйте кеширование:
// DevTools -> Network -> Disable cache
// Или в коде
fetch(url, { cache: 'no-store' }); // Всегда свежее
fetch(url, { cache: 'force-cache' }); // Только кеш
fetch(url, { cache: 'default' }); // Стандартное поведение
Итог
Браузер решает кешировать на основе:
- Cache-Control заголовков от сервера (основной механизм)
- ETag и Last-Modified для проверки валидности
- Service Workers для гибкого контроля
- Heuristic expiration (редко, если заголовков нет)
Правильное кеширование — это один из главных факторов производительности веба.