Skip to content
21 / 36

Что такое Time to First Byte (TTFB) и как его оптимизировать?


Полный ответ

TTFB складывается из нескольких этапов:

JavaScript
Браузер                                    Сервер
|  |
| --- DNS Lookup ---------------------------> |
| --- TCP Handshake ------------------------> |
| --- TLS Handshake (если HTTPS) ----------> |
| --- Отправка HTTP-запроса ----------------> |
| [Обработка на сервере] |
| <-- Первый байт ответа ------------------- |
|  |
| <====== Всё это TTFB ===============> |

Измерение TTFB через Performance API:

JavaScript
// Через Navigation Timing API
const [entry] = performance.getEntriesByType('navigation');

const ttfb = entry.responseStart - entry.requestStart;
console.log(`TTFB: ${ttfb.toFixed(2)} ms`);

// Более детальная разбивка
const dns = entry.domainLookupEnd - entry.domainLookupStart;
const tcp = entry.connectEnd - entry.connectStart;
const tls = entry.secureConnectionStart > 0
? entry.connectEnd - entry.secureConnectionStart
: 0;
const serverTime = entry.responseStart - entry.requestStart;

console.log(`DNS: ${dns.toFixed(2)} ms`);
console.log(`TCP: ${tcp.toFixed(2)} ms`);
console.log(`TLS: ${tls.toFixed(2)} ms`);
console.log(`Server processing: ${serverTime.toFixed(2)} ms`);

Способы оптимизации:

JavaScript
// 1. Серверный кеш -- самый большой выигрыш

// Express + node-cache
import NodeCache from 'node-cache';

const cache = new NodeCache({ stdTTL: 300 }); // 5 минут

app.get('/api/products', (req, res) => {
    const cached = cache.get('products');
    if (cached) {
      return res.json(cached); // мгновенный ответ из кеша
    }

    const products = db.query('SELECT * FROM products');
    cache.set('products', products);
    res.json(products);
  });

JavaScript
// 2. Оптимизация БД-запросов
// ❌ N+1 запросов -- каждый пользователь порождает отдельный запрос
app.get('/api/posts', async (req, res) => {
    const posts = await db.query('SELECT * FROM posts LIMIT 20');
    for (const post of posts) {
      post.author = await db.query(
        'SELECT * FROM users WHERE id = ?', [post.authorId]
      );
    }
    res.json(posts);
  });

// ✅ Один запрос с JOIN
app.get('/api/posts', async (req, res) => {
    const posts = await db.query(`
      SELECT p.*, u.name as authorName, u.avatar as authorAvatar
      FROM posts p
      JOIN users u ON p.authorId = u.id
      LIMIT 20
      `);
    res.json(posts);
  });

HTML
<!-- 3. Ресурсные подсказки в HTML -->
<!-- DNS prefetch для сторонних доменов -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- Preconnect -- DNS + TCP + TLS заранее -->
<link rel="preconnect" href="https://api.example.com" crossorigin>
<!-- 103 Early Hints -- сервер отправляет подсказки до основного ответа -->

Nginx

# 4. Настройка CDN / nginx для кеширования

# nginx.conf
proxy_cache_path /var/cache/nginx levels=1:2
  keys_zone=app_cache:10m max_size=1g inactive=60m;

server {
    location /api/ {
        proxy_cache app_cache;
        proxy_cache_valid 200 10m;
        proxy_cache_use_stale error timeout updating;
        proxy_pass http://backend;
    }
}

📌 Реальные кейсы

Диагностика медленного TTFB:

JavaScript
// Скрипт для мониторинга TTFB в production
const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (entry.entryType === 'navigation') {
        const ttfb = entry.responseStart - entry.requestStart;

        // Отправляем метрику в аналитику
        if (ttfb > 800) {
          sendToAnalytics('slow_ttfb', {
              ttfb,
              url: entry.name,
              transferSize: entry.transferSize,
              serverTiming: entry.serverTiming,
            });
        }
      }
    }
  });

observer.observe({ type: 'navigation', buffered: true });

Server-Timing заголовок для отладки серверной части:

JavaScript
// Сервер показывает, на что ушло время
app.get('/api/data', async (req, res) => {
    const start = performance.now();

    const dbStart = performance.now();
    const data = await db.query('SELECT ...');
    const dbTime = performance.now() - dbStart;

    const cacheStart = performance.now();
    await redis.set('key', JSON.stringify(data));
    const cacheTime = performance.now() - cacheStart;

    res.set('Server-Timing', [
        `db;dur=${dbTime.toFixed(1)};desc="Database"`,
        `cache;dur=${cacheTime.toFixed(1)};desc="Cache write"`,
        `total;dur=${(performance.now() - start).toFixed(1)}`,
      ].join(', '));

    res.json(data);
  });

// Видно в DevTools на вкладке Network (Timing)

Резюме

TTFB -- первая метрика в цепочке загрузки страницы. Если TTFB медленный, все остальные метрики (FCP, LCP) будут плохими автоматически. Основные точки оптимизации: серверный кеш, CDN, оптимизация запросов к БД, preconnect/dns-prefetch, HTTP/2 и 103 Early Hints.