TTFB (Time to First Byte), это время от отправки HTTP-запроса до получения первого байта ответа сервера. В 2026 году веб-производительность считается здоровой при TTFB ≤ 200 мс для 75-го перцентиля пользователей и «требует внимания» между 200 и 800 мс, если верить данным Chrome UX Report. Ниже я разбираю, как реально сокращаю TTFB на проде: edge-кэширование через CDN, инкрементальная статическая регенерация (ISR), HTTP/3, 103 Early Hints, стриминговый SSR и точечная настройка бэкенда, с рабочими конфигами, а не общими советами.
Целевое значение TTFB на 2026 год не превышает 200 мс на 75-м перцентиле; порог «нужна оптимизация» начинается с 800 мс (данные web.dev и CrUX).
TTFB напрямую влияет на LCP и косвенно на INP: любая миллисекунда до первого байта откладывает начало парсинга HTML и загрузку критических ресурсов.
Edge-кэширование с корректно настроенным Cache-Control: s-maxage и stale-while-revalidate обычно снижает TTFB c 400–800 мс до 20–60 мс.
ISR (Next.js, Astro, Nuxt) даёт статический TTFB для динамических страниц: генерация вне запроса, отдача из CDN.
HTTP/3 (QUIC) сокращает время рукопожатия на 100–300 мс на мобильных сетях за счёт объединения TLS и транспортного handshake.
103 Early Hints позволяют начать preload критических ресурсов ещё до готовности основного ответа, фактически параллеля TTFB и загрузку ассетов.
Что такое TTFB и почему он важен в 2026
TTFB измеряется браузером как разница между началом навигации (navigationStart) и моментом, когда получен первый байт основного HTML-документа (responseStart). Внутри этого окна упаковано много этапов: DNS-резолвинг, TCP-рукопожатие, TLS-переговоры, отправка запроса, ожидание на сервере (server think time) и передача первого байта по сети. Каждый из этапов оптимизируется своими средствами.
Формально TTFB не входит в Core Web Vitals, но глубоко влияет на две ключевые метрики. LCP не может стартовать раньше, чем браузер получил HTML и распарсил критический CSS. INP страдает косвенно: медленный TTFB часто означает, что и последующие XHR/fetch на этом же бэкенде идут медленно, а обработчики событий блокируются. По данным официального руководства web.dev по TTFB, каждые сэкономленные 100 мс TTFB транслируются примерно в такие же 100 мс улучшения LCP на среднестатистической странице.
В моей практике TTFB, это первое, что я проверяю у клиентов с плохим LCP. Часто оказывается, что фронтендщики уже применили все возможные приёмы (lazy-loading, WebP, preload), а корень проблемы совсем в другом: SSR-сервер, отдающий HTML за 900 мс из-за N+1 запроса к БД. Честно, я на такой сценарий натыкался столько раз, что теперь начинаю аудит именно с бэкенда.
Как правильно измерить TTFB: lab и field-данные
Есть два принципиально разных источника данных о TTFB: лабораторные (WebPageTest, Lighthouse, Chrome DevTools) и полевые (RUM, CrUX) от реальных пользователей. Для принятия решений опирайтесь именно на field-данные, лабораторные измерения из офиса в Москве не покажут, что для пользователя в Иркутске TTFB стабильно > 1.2 c.
В браузере достаточно короткого сниппета через PerformanceNavigationTiming:
// Измеряем TTFB и отправляем в аналитику
new PerformanceObserver((list) => {
for (const entry of list.getEntriesByType('navigation')) {
const ttfb = entry.responseStart - entry.requestStart;
// Учитываем redirect: если он был, добавляем redirect time
const total = entry.responseStart - entry.startTime;
console.log({ ttfb, total, redirectTime: entry.redirectEnd - entry.redirectStart });
// Отправляем в аналитику (Beacon для надёжности при выгрузке страницы)
navigator.sendBeacon('/rum', JSON.stringify({ metric: 'ttfb', value: total }));
}
}).observe({ type: 'navigation', buffered: true });
Библиотека web-vitals сама считает TTFB и присылает его через колбэк onTTFB(). Я разбирал сбор всех Core Web Vitals метрик, включая TTFB, в руководстве по Real User Monitoring и CrUX. Там же описал, как разбивать данные по перцентилям (p50/p75/p95), а не смотреть среднее, которое почти всегда лжёт.
Какое значение TTFB считается хорошим?
Google в 2026 году держит те же пороги, что и в 2023: до 800 мс это «хорошо» (пропускной, минимум), до 1800 мс «требует улучшения», выше «плохо». Но эти цифры устарели: конкурентные сайты выходят на 100–200 мс, а Cloudflare и Vercel рапортуют, что 60% их клиентских проектов держат TTFB под 100 мс на статике из edge. Ниже таблица моих внутренних порогов, которые я использую в аудитах.
Класс страницы
Цель p75 TTFB
Порог тревоги p75
Основной рычаг
Статический маркетинг
< 100 мс
> 250 мс
Edge cache + Brotli
SSR продуктовые страницы
< 200 мс
> 500 мс
ISR / edge-функции
Персонализированный SSR
< 400 мс
> 800 мс
Стриминг + БД-кэш
API-ответ (JSON)
< 150 мс
> 350 мс
Индексы, connection pooling
Админка / dashboard
< 500 мс
> 1200 мс
Prefetch + skeleton
Что вызывает высокий TTFB: разбор по компонентам
Разбираю каждый компонент TTFB на реальном примере: приложение на Next.js 15, размещённое на VPS в европейском датацентре, пользователь в Санкт-Петербурге.
1. DNS lookup: 20-120 мс
Первый визит требует резолвинга. Решение: короткий TTL с географическим DNS (Route53, Cloudflare DNS), preconnect с других страниц через <link rel="dns-prefetch"> и <link rel="preconnect">.
2. TCP handshake: 30-150 мс
Три пакета туда-обратно (SYN, SYN-ACK, ACK). Ничего не оптимизируем, если только не переключимся на HTTP/3 (см. ниже).
3. TLS handshake: 50-200 мс
TLS 1.3 сократил рукопожатие до одного round-trip, а 0-RTT для повторных подключений до нуля. Убедитесь, что сервер отдаёт TLS 1.3 (проверить: openssl s_client -connect example.com:443 -tls1_3) и включён OCSP stapling.
4. Server think time: 50-2000 мс
Самая большая переменная. Здесь живут SSR-рендер, запросы к БД, вызовы внешних API, шаблонизация. Именно эту часть режет ISR и кэширование.
5. Отдача первого байта: 5-50 мс
Практически константа при HTTP/2 и HTTP/3, страдает только при пиковой нагрузке или нехватке файловых дескрипторов на сервере.
# Смотрим разбивку TTFB через curl (полезно для CI)
curl -w "@-" -o /dev/null -s https://example.com/ <<'EOF'
DNS lookup: %{time_namelookup}s
TCP connect: %{time_connect}s
TLS handshake: %{time_appconnect}s
Server time: %{time_starttransfer}s
Total: %{time_total}s
HTTP version: %{http_version}
EOF
Edge-кэширование и CDN: главный рычаг для снижения TTFB
Как снизить TTFB быстрее всего? Раздавать HTML из ближайшего к пользователю POP’а. Средний RTT от Москвы до Frankfurt равен 30-45 мс; до POP-точки в самой Москве, всего 3-8 мс. Только это уже отсекает 60-80% сетевой части TTFB.
Ключевая механика, это правильные HTTP-заголовки. Cache-Control: s-maxage=60, stale-while-revalidate=3600 говорит CDN хранить ответ 60 секунд как «свежий», а следующий час отдавать как «протухший, но с фоновым обновлением». В браузере пользователь видит мгновенный ответ; на бэкенд запрос идёт максимум раз в 60 секунд с одного POP’а.
// Next.js 15 App Router: настраиваем edge-кэш через Response headers
// app/products/[id]/page.tsx
export const revalidate = 60; // ISR: перегенерация раз в 60 с
// Или через явные заголовки в route handler:
// app/api/catalog/route.ts
export async function GET() {
const data = await fetchCatalog();
return Response.json(data, {
headers: {
// s-maxage — для CDN, max-age для браузера
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=3600',
'CDN-Cache-Control': 'public, s-maxage=300', // Cloudflare/Fastly
'Vary': 'Accept-Encoding, Accept-Language',
},
});
}
Отдельно про инвалидацию, моё любимое. Классический подход «purge всего кэша» болезнен: CDN холодный, TTFB прыгает до 800 мс, пока не прогреется. Правильно, теговая инвалидация: помечаем ответы Cache-Tag: product-{id}, catalog и точечно вычищаем при событии в БД. Cloudflare Enterprise и Fastly поддерживают это из коробки; в Next.js есть revalidateTag().
ISR и статическая регенерация: как получить TTFB как у статики
ISR (Incremental Static Regeneration), паттерн, который придумали в Vercel для Next.js, а сейчас его копируют Astro (on-demand), Nuxt (routeRules.isr), SvelteKit (prerender.entries) и даже Gatsby. Идея простая: страница генерируется вне запроса пользователя (либо при билде, либо в фоне после первого хита) и лежит на диске или в edge-кэше как статический HTML. TTFB такой страницы равен TTFB статики, обычно 20-50 мс.
// Next.js 15: App Router с ISR
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
// Генерируем 100 самых популярных постов на билде,
// остальные — по запросу (fallback: 'blocking' поведение)
export async function generateStaticParams() {
const posts = await fetch('https://cms.example.com/posts?limit=100').then(r => r.json());
return posts.map((p: { slug: string }) => ({ slug: p.slug }));
}
// Перегенерация раз в 5 минут (300 с)
export const revalidate = 300;
// Разрешаем ISR для параметров, не сгенерированных на билде
export const dynamicParams = true;
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await fetch(`https://cms.example.com/posts/${slug}`, {
// Тегируем для точечной инвалидации через revalidateTag
next: { tags: [`post-${slug}`] },
}).then(r => r.ok ? r.json() : null);
if (!post) notFound();
return <article dangerouslySetInnerHTML={{ __html: post.html }} />;
}
При публикации нового поста в CMS хук вызывает revalidateTag('post-my-slug'), и следующий запрос перерисует страницу. Все промежуточные запросы получают старую версию мгновенно. Никаких водопадов и холодных стартов.
Astro 5.x реализует то же через experimental.i18n и edge middleware; я сравнивал подходы в разборе стратегий кэширования Service Worker. Там речь про клиентский слой, но stale-while-revalidate логика идентична на CDN.
HTTP/3 и QUIC: сокращаем рукопожатие
HTTP/3 работает поверх QUIC (UDP), а не TCP. Ключевое преимущество для TTFB: TLS 1.3 handshake встроен в транспортный уровень. Вместо TCP+TLS = 2 RTT получаем 1 RTT для первого подключения и 0-RTT для повторных. На мобильном 4G с RTT в 80 мс это экономит до 160 мс на первом запросе.
По данным Cloudflare (сентябрь 2026), 34% всего HTTPS-трафика в их сети уже идёт по HTTP/3. Поддержка в браузерах: Chrome 106+, Firefox 88+, Safari 14+. Проверить, что ваш сайт отдаёт HTTP/3:
Для включения HTTP/3 на nginx (нужен nginx 1.25+ с --with-http_v3_module):
server {
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
ssl_protocols TLSv1.3;
# Анонс поддержки HTTP/3 клиенту
add_header alt-svc 'h3=":443"; ma=86400';
# 0-RTT (early data), с осторожностью, есть replay attack surface
ssl_early_data on;
}
103 Early Hints: параллелим TTFB и загрузку ресурсов
103 Early Hints, редкий случай, когда HTTP RFC (см. RFC 8297 «Indicating Hints») решает практическую проблему. Сервер может отправить промежуточный ответ 103 с заголовками Link: preload ещё до готовности основного HTML. Браузер увидит его, начнёт качать CSS/шрифты, и когда придёт финальный 200-ответ, критические ресурсы уже в кэше.
// Express-подобный псевдокод для Node.js бэкенда с ранними подсказками
app.get('/product/:id', async (req, res) => {
// Отправляем 103 ДО обращения к БД: секунду выигрываем "бесплатно"
res.writeEarlyHints({
link: [
'</css/product.css>; rel=preload; as=style',
'</fonts/inter.woff2>; rel=preload; as=font; crossorigin',
'</js/product-hero.js>; rel=preload; as=script',
],
});
// Медленная часть: обращение к БД, микросервисам и т.д.
const product = await slowQuery(req.params.id);
res.send(renderProductPage(product));
});
Поддержка: Node.js 18.11+, Cloudflare, Fastly, Vercel, Fastify (нативно). На платформе Vercel Early Hints автоматически посылаются для preload-ассетов, объявленных в <head>. Детальный разбор комбинации 103 с fetchpriority и preconnect есть в моём гайде по приоритизации загрузки ресурсов.
Настройка бэкенда: база данных, компрессия, keep-alive
Даже с идеальным CDN бэкенд-стрелка стреляет в TTFB каждый раз, когда кэш инвалидируется. Три технических долга, которые я вижу чаще всего.
N+1 в SSR-рендере
SSR рендерит страницу, для каждого элемента списка бьёт в БД отдельным запросом. Профилируйте через ORM-логгер (Prisma log: ['query']) или Server-Timing заголовки. Решение: SELECT ... JOIN или dataloader с батчингом. Я хитанул на этом свежий Next.js-проект в прошлом квартале, TTFB на listing-странице был 1.4 с; после dataloader стал 180 мс.
Отсутствие compression
Brotli на nginx: brotli on; brotli_types text/html application/json; brotli_comp_level 4;. Уровень 4 даёт компромисс между CPU и размером. Динамический HTML жмётся в 4-5 раз, что снижает сетевую часть TTFB и, что важнее, ускоряет транспорт первого чанка при стриминге.
HTTP keep-alive и connection pooling
Между приложением и БД держите пул соединений (pg-pool, mysql2 pool). Каждое новое соединение, это TCP+TLS handshake до БД, легко +50 мс к TTFB. То же для upstream HTTP: agentkeepalive в Node.js. О размерах бандла и последствиях для парсинга я подробнее писал в гайде по оптимизации JavaScript-бандла, там про сторону клиента, но связь с TTFB очевидна: чем меньше HTML+JS, тем короче транспорт.
Классический SSR ждёт готовности всей страницы, только потом отдаёт HTML: плохой TTFB. Стриминговый SSR (React 19 с renderToPipeableStream, Next.js App Router с Suspense) отправляет HTML чанками по мере готовности: сначала shell (навигация, footer), потом медленные секции. TTFB становится равным времени отдачи shell, обычно 30-80 мс.
// Next.js 15: стриминг с помощью Suspense
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { SlowChart, SlowTable, HeaderShell } from './components';
export default function Dashboard() {
return (
<>
{/* Отдаётся мгновенно: определяет TTFB */}
<HeaderShell />
<Suspense fallback={<SkeletonChart />}>
<SlowChart /> {/* стримится позже */}
</Suspense>
<Suspense fallback={<SkeletonTable />}>
<SlowTable />
</Suspense>
</>
);
}
Один нюанс, за который меня когда-то ругали ребята из SRE: стриминг ломает Content-Length и требует Transfer-Encoding: chunked. Если перед вашим сервером стоит обратный прокси, он должен поддерживать chunked-передачу (nginx делает это по умолчанию, некоторые enterprise-LB нет).
Определите текущий тип страниц (статика / ISR / SSR / персонализированный) и целевое значение из таблицы выше.
Включите HTTP/3 и TLS 1.3 на сервере/CDN. Проверьте alt-svc и curl --http3.
Настройте Cache-Control: s-maxage + stale-while-revalidate для всех кэшируемых ответов.
Внедрите ISR или полную статическую генерацию для маркетинговых и продуктовых страниц.
Добавьте 103 Early Hints для критических ресурсов (CSS, hero-шрифт, hero-скрипт).
Проаудитьте бэкенд: N+1 запросы, отсутствие индексов, connection pooling.
Включите Brotli со сжатием уровня 4 для HTML/JSON.
Переведите SSR-страницы на стриминговый рендер с Suspense-границами.
Настройте теговую инвалидацию (Cache-Tag/revalidateTag) вместо purge-all.
Отправляйте Server-Timing и мониторьте регрессии в CI (Lighthouse CI, WebPageTest scripts).
Часто задаваемые вопросы
Влияет ли TTFB на SEO в 2026 году?
Прямо нет: TTFB не входит в Core Web Vitals и не является отдельным сигналом ранжирования. Косвенно да: высокий TTFB тянет за собой плохой LCP, а LCP уже влияет на ранжирование. Google Search Console показывает TTFB как диагностический параметр к LCP.
Как быстро снизить TTFB, если нет доступа к бэкенду?
Поставьте CDN перед origin (Cloudflare Free, Fastly, Bunny.net), настройте кэширование HTML с коротким s-maxage и stale-while-revalidate, включите Brotli. Это стандартно снижает TTFB в 3-10 раз без изменения кода приложения.
Что важнее для LCP, низкий TTFB или preload изображения?
Оба, но TTFB это фундамент. Пока не пришёл HTML, браузер не знает, что грузить: preload-хинт стартует после парсинга. 103 Early Hints частично решают эту проблему, отправляя preload-ссылки ещё до основного ответа.
Можно ли иметь низкий TTFB на персонализированной странице?
Да, при правильной сегментации кэша. Разделите публичную часть страницы (кэшируемую) и персональную (fetch на клиенте после первичной отрисовки, или через ESI/edge includes на CDN). Vercel и Cloudflare предлагают edge middleware, где персонализация вычисляется за 5-15 мс.
Как отличить проблему в сети от проблемы на сервере?
Сравните time_starttransfer и time_connect в curl. Если между ними больше 100-200 мс, это server think time (медленный бэкенд, БД, шаблонизация). Если много уходит на connect/appconnect, это сеть, DNS или TLS-рукопожатие. Server-Timing заголовок из бэкенда разложит серверную часть по компонентам.
Перенесите GTM, GA4 и Meta Pixel в web worker через Partytown, замените тяжёлые встраивания фасадами и держите бюджет third-party в CI. Реальные замеры TBT, INP и LCP из production.
Полный разбор Service Worker и стратегий кэширования в 2026: Workbox 7, stale-while-revalidate для статики, NetworkFirst для HTML, Navigation Preload и офлайн-фолбэки с рабочими примерами кода.