9. Network Performance
Сетевые запросы — одно из главных узких мест. Каждый запрос = задержка DNS + TCP + TLS + TTFB.
HTTP/2 и HTTP/3
Заголовок раздела «HTTP/2 и HTTP/3»HTTP/1.1: Один запрос за раз, очередьHTTP/2: Мультиплексирование — много запросов в одном соединенииHTTP/3: UDP вместо TCP, лучше при потерях пакетовПроверка: Chrome DevTools → Network → Protocol колонка
Resource Hints
Заголовок раздела «Resource Hints»<!-- DNS Prefetch — заранее резолвим DNS --><link rel="dns-prefetch" href="//api.example.com"><link rel="dns-prefetch" href="//fonts.googleapis.com"><link rel="dns-prefetch" href="//cdn.example.com">
<!-- Preconnect — TCP + TLS рукопожатие заранее --><link rel="preconnect" href="https://api.example.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Preload — загружаем критический ресурс заранее --><link rel="preload" href="/fonts/main.woff2" as="font" crossorigin><link rel="preload" href="/hero.webp" as="image" fetchpriority="high"><link rel="preload" href="/critical.css" as="style"><link rel="preload" href="/main.js" as="script">
<!-- Prefetch — загружаем следующую страницу --><link rel="prefetch" href="/about.html"><link rel="prefetch" href="/assets/next-page-bundle.js">
<!-- Modulepreload — для ES-модулей --><link rel="modulepreload" href="/app.js">Gzip / Brotli на сервере
Заголовок раздела «Gzip / Brotli на сервере»# Nginx — Gzipgzip on;gzip_comp_level 6;gzip_types text/plain text/css application/javascript application/json;
# Nginx — Brotli (быстрее и лучше сжимает)brotli on;brotli_comp_level 6;brotli_types text/plain text/css application/javascript;// Next.js — автоматически// Compress включён по умолчанию
// Expressconst compression = require('compression');app.use(compression({ level: 6 }));Результат: JS/CSS файлы сжимаются в 3-5 раз!
Кэширование HTTP
Заголовок раздела «Кэширование HTTP»# Nginx — правильные Cache-Control заголовкиlocation /assets/ { # Статика с хешем в имени — кэшируем навсегда add_header Cache-Control "public, max-age=31536000, immutable";}
location /api/ { # API — без кэша или короткий add_header Cache-Control "no-cache, no-store, must-revalidate";}
location = /index.html { # HTML — проверяем актуальность add_header Cache-Control "no-cache";}// Service Worker — продвинутое кэшированиеconst CACHE_NAME = 'app-v1';const STATIC_ASSETS = ['/', '/styles.css', '/app.js'];
self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS)) );});
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then(cached => { return cached || fetch(event.request).then(response => { caches.open(CACHE_NAME).then(cache => cache.put(event.request, response.clone())); return response; }); }) );});Оптимизация API запросов
Заголовок раздела «Оптимизация API запросов»GraphQL — только нужные поля
Заголовок раздела «GraphQL — только нужные поля»// ❌ REST — получаем всёGET /api/users/123 → { id, name, email, avatar, bio, settings, posts, ... }
// ✅ GraphQL — только нужноеquery { user(id: "123") { name avatar }}Request Batching
Заголовок раздела «Request Batching»// ❌ 10 отдельных запросовfor (const id of userIds) { await fetch(`/api/users/${id}`); // 10 round trips!}
// ✅ Один батч-запросconst users = await fetch('/api/users/batch', { method: 'POST', body: JSON.stringify({ ids: userIds }),});
// ✅ DataLoader (для GraphQL/Node.js)const loader = new DataLoader(async (ids) => { const users = await db.user.findMany({ where: { id: { in: ids } } }); return ids.map(id => users.find(u => u.id === id));});Request Deduplication
Заголовок раздела «Request Deduplication»// Кэшируем одинаковые запросыconst requestCache = new Map();
async function fetchWithCache(url) { if (requestCache.has(url)) { return requestCache.get(url); // возвращаем существующий Promise! }
const promise = fetch(url).then(r => r.json()); requestCache.set(url, promise);
// Очищаем после успешной загрузки promise.then(() => requestCache.delete(url));
return promise;}
// При параллельных вызовахfetchWithCache('/api/user'); // создаёт запросfetchWithCache('/api/user'); // возвращает тот же Promise — один запрос!Стриминг ответов
Заголовок раздела «Стриминг ответов»// Server-Sent Events для обновлений в реальном времениconst eventSource = new EventSource('/api/stream');
eventSource.addEventListener('update', (e) => { updateUI(JSON.parse(e.data));});
// WebSocket для двунаправленной связиconst ws = new WebSocket('wss://api.example.com/ws');
ws.addEventListener('message', ({ data }) => { updateUI(JSON.parse(data));});
// Next.js App Router — Response Streamingexport default async function Page() { return ( <Suspense fallback={<Loading />}> <SlowDataComponent /> {/* Стримится как готово */} </Suspense> );}