Перейти к содержимому

10. Caching Strategies

Кэширование — это сохранение результатов дорогих операций для повторного использования. Правильная стратегия = быстрый сайт без лишних запросов.

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";
}
# Изображения — долго, но с revalidation
location ~* \.(webp|jpg|png|svg)$ {
add_header Cache-Control "public, max-age=2592000"; # 30 дней
}
# HTML — проверяем каждый раз
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# API — зависит от endpoint
location /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";
}
// 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 — умное кэширование
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,
});
}
// Node.js — Simple in-memory cache
const 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 — встроенный кэш fetch
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // ISR: обновляем раз в час
});
return res.json();
}