10. Caching Strategies
Кэширование — это сохранение результатов дорогих операций для повторного использования. Правильная стратегия = быстрый сайт без лишних запросов.
HTTP Cache Headers
Заголовок раздела «HTTP Cache Headers»Cache-Control: public, max-age=31536000, immutable public — можно кэшировать CDN и браузером max-age — время жизни в секундах immutable — файл никогда не изменится (хеш в имени)
Cache-Control: no-cache — всегда проверяй актуальность (ETag/Last-Modified)
Cache-Control: no-store — никогда не кэшировать (приватные данные)
Cache-Control: stale-while-revalidate=60 — показывай кэш, обновляй в фонеСтратегии для разных ресурсов
Заголовок раздела «Стратегии для разных ресурсов»# Статика с хешем (main.abc123.js) — кэшируем навсегдаlocation ~* \.(js|css|woff2)$ { add_header Cache-Control "public, max-age=31536000, immutable";}
# Изображения — долго, но с revalidationlocation ~* \.(webp|jpg|png|svg)$ { add_header Cache-Control "public, max-age=2592000"; # 30 дней}
# HTML — проверяем каждый разlocation ~* \.html$ { add_header Cache-Control "no-cache";}
# API — зависит от endpointlocation /api/static-data { add_header Cache-Control "public, max-age=3600, stale-while-revalidate=86400";}location /api/user { add_header Cache-Control "private, no-cache";}Service Worker Caching
Заголовок раздела «Service Worker Caching»// sw.js — стратегии кэширования
const CACHE_V = 'v1';
// Cache First — статика (JS, CSS, шрифты)async function cacheFirst(request) { const cached = await caches.match(request); if (cached) return cached;
const response = await fetch(request); const cache = await caches.open(CACHE_V); cache.put(request, response.clone()); return response;}
// Network First — API (актуальные данные)async function networkFirst(request) { try { const response = await fetch(request); const cache = await caches.open(CACHE_V); cache.put(request, response.clone()); return response; } catch { return caches.match(request); // fallback на кэш }}
// Stale While Revalidate — баланс скорости и актуальностиasync function staleWhileRevalidate(request) { const cached = await caches.match(request);
const fetchPromise = fetch(request).then(response => { caches.open(CACHE_V).then(cache => cache.put(request, response.clone())); return response; });
return cached || fetchPromise; // сразу кэш, обновляем в фоне}
self.addEventListener('fetch', (event) => { const { request } = event;
if (request.url.includes('/api/')) { event.respondWith(networkFirst(request)); } else if (request.destination === 'image') { event.respondWith(staleWhileRevalidate(request)); } else { event.respondWith(cacheFirst(request)); }});React Query / SWR — Клиентский кэш
Заголовок раздела «React Query / SWR — Клиентский кэш»// React Query — умное кэшированиеimport { useQuery, useQueryClient } from '@tanstack/react-query';
function UserProfile({ userId }) { const { data, isLoading } = useQuery({ queryKey: ['user', userId], queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()), staleTime: 5 * 60 * 1000, // 5 мин — данные "свежие" gcTime: 10 * 60 * 1000, // 10 мин — хранить в памяти refetchOnWindowFocus: false, // не рефетчить при фокусе окна });
return <div>{data?.name}</div>;}
// Prefetch — загружаем заранееconst queryClient = useQueryClient();
function UserCard({ userId }) { return ( <div onMouseEnter={() => { queryClient.prefetchQuery({ queryKey: ['user', userId], queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()), }); }} > <Link to={`/users/${userId}`}>Профиль</Link> </div> );}
// SWR — альтернативаimport useSWR from 'swr';
function Dashboard() { const { data } = useSWR('/api/stats', fetcher, { refreshInterval: 30000, // обновлять каждые 30 сек revalidateOnFocus: true, });}Memoization на уровне сервера
Заголовок раздела «Memoization на уровне сервера»// Node.js — Simple in-memory cacheconst cache = new Map();
async function getCachedData(key, fetchFn, ttl = 60000) { const cached = cache.get(key); if (cached && Date.now() < cached.expires) { return cached.data; }
const data = await fetchFn(); cache.set(key, { data, expires: Date.now() + ttl }); return data;}
// Redis кэш (для production)import Redis from 'ioredis';const redis = new Redis(process.env.REDIS_URL);
async function getWithRedis(key, fetchFn, ttl = 3600) { const cached = await redis.get(key); if (cached) return JSON.parse(cached);
const data = await fetchFn(); await redis.setex(key, ttl, JSON.stringify(data)); return data;}
// Next.js — встроенный кэш fetchasync function getData() { const res = await fetch('https://api.example.com/data', { next: { revalidate: 3600 } // ISR: обновляем раз в час }); return res.json();}