Senior Frontend Architect, 10+ лет опыта построения production-проектов на Next.js. Contentful Certified Professional (2024). Специализация: React Server Components, headless eCommerce, инженерия Core Web Vitals.
Веб-производительность в 2026 году - это уже не дисциплина микро-оптимизаций, а сумма архитектурных решений на двенадцати слоях стека. В этой статье я разбираю каждый слой через протокольную механику, конвейеры рендеринга браузеров и реальные данные HTTP Archive 2025 Web Almanac и Chrome User Experience Report (CrUX). По каждому столпу: какое физическое или вычислительное ограничение создаёт узкое место, какой инженерный примитив его устраняет, и как это измеримо влияет на Core Web Vitals - LCP, INP и CLS.
Сайт, корректный на всех двенадцати столпах, не просто «ощущается быстрее» - он измеримо входит в верхние 10% веба. Deloitte Digital в исследовании *Milliseconds Make Millions* (2020) показали: улучшение скорости мобильного сайта на 0,1 секунды увеличивает конверсию в ритейле на 8,4% и средний чек на 9,2%. Обратное тоже верно: слабость даже на одном фундаментальном столпе - обычно это столп 1 (транспорт) или столп 10 (диагностика) - обрушит общий результат независимо от усилий на остальных.
Столп 1 - Физика сети: транспортные протоколы и компрессия
Любой разговор о производительности начинается на физическом уровне, потому что задержка ограничена скоростью света. Round-trip Нью-Йорк - Франкфурт по оптоволокну - около 80мс. Это жёсткий физический пол, который никакая клиентская оптимизация опустить не способна. Инженерия может контролировать количество round-trip, необходимых для доставки страницы - именно поэтому выбор транспортного протокола важнее почти любого другого решения.
HTTP/1.1 требует до шести последовательных TCP-соединений на origin, каждое со своим TLS-handshake (~2 RTT) и TCP slow-start. На нестабильном 4G, где потери пакетов в полевых данных составляют 2–4%, архитектура рушится в Head-of-Line blocking: один потерянный TCP-сегмент стопорит все последующие потоки. HTTP/2 мультиплексирует потоки по одному соединению, но HoL-блокировка осталась на уровне TCP из-за его гарантии in-order доставки байтов. HTTP/3 (RFC 9114, июнь 2022) перестраивает HTTP поверх QUIC (RFC 9000) - UDP-транспорта, где каждый поток независимо надёжен. Потеря пакета в потоке A больше не блокирует поток B. По публичным данным Cloudflare, HTTP/3 снижает 99-й перцентиль времени загрузки страницы на проблемных мобильных сетях на 18–34% по сравнению с HTTP/2.
Поверх транспорта - компрессия. Brotli (RFC 7932) разработан Google специально для веба: его статический словарь предобучен на корпусе типичных HTML/CSS/JS токенов, что даёт 15–25% преимущества по размеру над gzip на текстовых ассетах. Плата асимметрична: Brotli-11 сжимает примерно в 60 раз медленнее gzip-6, поэтому применять его можно только на этапе билда, не на лету. Корректная архитектура: пред-компрессия на CI/CD (варианты .br и .gz оба кладутся на CDN origin), выбор через Accept-Encoding, fallback на gzip только для клиентов без поддержки Brotli (в 2026 их практически нет).
- Архитектурное правило: HTTP/3 на порту 443 с Alt-Svc. Откат на HTTP/2 для клиентов без QUIC. Продакшн-бандл не должен отдаваться по HTTP/1.1, кроме случаев юридических ограничений.
- Правило компрессии: Brotli-11 на все текстовые ассеты (HTML, CSS, JS, JSON, SVG, шрифты) на билде. Не сжимайте повторно уже сжатые бинарные форматы (AVIF, WebP, MP4, woff2) - это чистые потери.
- Измерение: проверяйте версию протокола в DevTools → Network → колонка Protocol.
http/1.1в проде - это не edge case, а ошибка конфигурации CDN.
Столп 2 - Конвейер изображений: инженерия форматов и проблема LCP
Изображения - самый тяжёлый класс ресурсов в современном вебе. По данным HTTP Archive 2025 Web Almanac, медианный вес изображений на мобильной странице - 1012 КБ, около 48% от всего трафика. Оптимизация изображений - это не деталь, а доминирующий рычаг влияния на LCP для ~85% страниц, где элементом Largest Contentful Paint является изображение.
Выбор формата - это компромисс между степенью сжатия и точностью воспроизведения, определяемый нижележащим видео-кодеком. JPEG (1992, DCT) - это пол. WebP (2010, VP8) даёт 25–35% выигрыша при той же воспринимаемой качестве. AVIF (2019, intra-кадры AV1) уменьшает файл ещё на 20–50% по сравнению с WebP и - что критично - поддерживает 10- и 12-битную глубину цвета и пространство BT.2020 для HDR. На типичной продуктовой фотографии 2048×2048 sRGB: JPEG Q80 = 420 КБ, WebP Q80 = 280 КБ, AVIF Q55 = 145 КБ - все при SSIM выше 0,98.
Элемент <picture> с source по типу - правильный механизм доставки: браузер сам выбирает лучший поддерживаемый формат без JS-round-trip. Кроме формата, *разрешение* должно соответствовать отрендеренному размеру. Hero-изображение 2000×1500, отданное на 390-пиксельный мобильный viewport, тратит впустую ~90% байтов. Это задача srcset + sizes: sizes декларирует отрендеренную ширину как CSS-выражение; браузер умножает на devicePixelRatio и выбирает ближайший дескриптор из srcset. Неправильный sizes - частая причина плохого LCP на внешне хорошо оптимизированных сайтах.
И наконец, LCP-изображение требует явной приоритизации. Атрибут fetchpriority="high" (Priority Hints, Chrome 101+) поднимает ресурс выше дефолтного medium, сигнализируя preload scanner отправить его до всех lazy-загрузок. loading="lazy" на не-LCP изображениях, наоборот, откладывает запрос до пересечения с viewport. Асимметрия намеренная: одно изображение - гиперприоритет, всё остальное - отсрочка.
- Декодирование:
decoding="async"уводит декод в compositor worker. На hero-изображениях (>500 КБ декодированного RGBA) это убирает 20–80мс стола главного потока, который иначе бьёт по INP во время скролла. - Явные размеры: каждый
<img>требует атрибутовwidthиheight(или CSSaspect-ratio). Без них браузер не может посчитать layout до прихода байтов - прямая причина CLS. - CDN-трансформация: матрица форматов и размеров генерируется на edge (Cloudflare Images, Vercel Image Optimization, imgix), не коммитится в git. Типичная матрица - 5 ширин × 3 формата = 15 вариантов на исходник.
Столп 3 - Типографика: метрики шрифтов и компромисс FOUT/FOIT
Веб-шрифты обманчиво дороги, потому что блокируют рендер по умолчанию. Пока файл шрифта не скачан, не декодирован и не сопоставлен с правилом @font-face, браузер стоит перед выбором: рендерить невидимый текст (FOIT - Flash of Invisible Text) или фоллбэк, который сместит вёрстку при приходе настоящего шрифта (FOUT - Flash of Unstyled Text). Неверный выбор здесь - прямая причина CLS.
Вариативные шрифты (OpenType 1.8, 2016) решают половину задачи. Вместо восьми woff2 на веса 100–900, один файл несёт непрерывную ось веса и интерполирует на лету. Свойство font-variation-settings открывает доступ к осям. Производственный Inter variable весит ~120 КБ woff2 - меньше, чем два статических веса. Экономия сети складывается с subsetting: вырезание неиспользуемых глифов (кириллица в английской сборке, CJK в латинской) обычно уменьшает файл в два раза.
Стратегию рендеринга задаёт font-display. Значения не взаимозаменяемы. swap показывает фоллбэк сразу и подменяет его при загрузке веб-шрифта - принимает FOUT, отвергает FOIT. optional даёт браузеру 100мс бюджета: если шрифт не пришёл, он пропускается для этой загрузки - правильный выбор для декоративных шрифтов. Дескрипторы size-adjust, ascent-override, descent-override позволяют подогнать метрики фоллбэка под веб-шрифт пиксель в пиксель, сводя CLS от swap к нулю.
- Preload LCP-шрифта:
<link rel="preload" as="font" type="font/woff2" crossorigin>. Пропускcrossoriginтихо инвалидирует preload, потому что шрифты всегда CORS-запрашиваются. - Self-hosting: Google Fonts через
fonts.googleapis.comдобавляет DNS-резолв, TCP/TLS handshake и непредсказуемую границу кеша. Self-hosting на основном origin быстрее в любом измерении. - Совпадение метрик фоллбэка: используйте screenspan.net/fallback для генерации значений
size-adjust,ascent-override,line-gap-override, обнуляющих сдвиг от swap.
Столп 4 - Архитектура JavaScript: модель стоимости гидратации
JavaScript - самые дорогие байты в вебе. 100 КБ изображения - это сеть и декодер; 100 КБ JavaScript - это сеть, парсер, компилятор, кеш байткода, главный поток (для выполнения) и V8-heap (для удержанных объектов). Анализ Эдди Османи *The Cost of JavaScript* (2022, актуализирован в 2025) показывает, что mid-range Android (Moto G Power) обрабатывает JS примерно в 4 раза медленнее high-end десктопа. Поэтому JS-тяжёлые сайты отлично работают в разработке и проваливаются в продакшене: железо разработчика не репрезентативно для поля.
React Server Components (RSC), стабилизированные в React 19 и дефолтные в Next.js 15+ - самый значимый архитектурный сдвиг со времён хуков. RSC выполняется только на сервере, эмитит сериализованный UI-output (не HTML, не VDOM - специализированный wire-формат) и не добавляет *ни одного байта* в клиентский бандл. Операционное правило: каждый компонент без state, эффектов, браузерных API и обработчиков событий должен быть RSC по умолчанию. Клиентские компоненты (граница 'use client') - явное исключение, а не норма.
Помимо RSC, Islands-архитектура (Джейсон Миллер, 2020) формализует частичную гидратацию. Каждый интерактивный виджет - независимо гидрируемый остров; статический HTML между островами не несёт JavaScript. Astro - каноническая реализация; Qwik идёт дальше с Resumability - сериализует состояние выполнения фреймворка в HTML и откладывает гидратацию до первой интеракции. По продакшн-бенчмаркам команды Qwik: 50 КБ Qwik-приложение отдаёт ~1 КБ начального JS против ~45 КБ эквивалентной React SPA.
- Цель билда:
es2024. Каждый полифил в основном бандле - налог на современные браузеры, которые в 2026 покрывают >96% трафика. - Dynamic import по интеракции:
const Modal = dynamic(() => import('./Modal'))откладывает модуль до рендера. Для по-настоящему тяжёлых виджетов (3D-сцены, rich-text редакторы, плееры) гейтьте импорт за обработчик события, а не за mount компонента. - Анализ бандла обязателен:
@next/bundle-analyzerилиrollup-plugin-visualizerдолжны крутиться в CI. Необъяснимые +20 КБ в main chunk - это регрессия, требующая ревью до merge.
Столп 5 - Critical Rendering Path: CSSOM, layout и containment
Рендеринг-пайплайн браузера имеет шесть стадий: Parse HTML → DOM → Parse CSS → CSSOM → Render Tree → Layout → Paint → Composite. CSS блокирует рендер по умолчанию, потому что без CSSOM браузер не может построить render tree. Именно поэтому <link rel="stylesheet"> в <head> напрямую гейтит First Contentful Paint.
Корректная архитектура инлайнит *критический* CSS - правила, необходимые для рендера above-the-fold - прямо в <head>, остальное откладывает. Next.js делает это автоматически через CSS-in-JS-интеграцию; для статических сайтов - Critters или Beasties (активный форк на 2025). Результат: FCP разблокирован сразу по приходу HTML, полный стиль-шит загружается асинхронно.
CSS Containment (W3C CSS Containment Module Level 2) даёт движку явное разрешение локализовать пересчёт стилей, layout и paint. contain: layout paint style на границе компонента обещает браузеру, что изменения внутри не затронут внешнее дерево - движок пропускает перерасчёт остального render tree. Родственное свойство content-visibility: auto идёт дальше: поддерево не layout'ится вовсе, пока не приближается к viewport. По примерам из спеки Containment, content-visibility: auto на архиве из 10 000 статей срезает начальную работу рендера на >90%.
- Contain-intrinsic-size: всегда пара к
content-visibility: auto, иначе CLS будет катастрофическим при появлении элементов в viewport. - Избегайте layout thrashing: чтение layout-зависимого свойства (
offsetWidth,getBoundingClientRect) после записи в DOM форсирует синхронный layout. Классика: сначала все чтения, затем все записи в пределах кадра. - Только CSS-анимации: анимируйте только
transformиopacity. Анимацияtop/left/width/heightтриггерит layout каждый кадр - на 60Hz это 16,67мс бюджета только на композицию.
Столп 6 - Управление сторонними скриптами: Facade и Partytown
Сторонние скрипты - тихий убийца CWV. HTTP Archive 2025 Web Almanac: медианная мобильная страница грузит 22 third-party-запроса суммарно на 520 КБ, топ - Google Tag Manager (~80 КБ min), Intercom/Zendesk (~350 КБ), встроенные плееры (~600 КБ). Каждый из них выполняется на главном потоке и напрямую вносит вклад в Total Blocking Time и INP.
Facade Pattern формализует инженерный ответ. Вместо эагерной загрузки виджета рендерится статическая заглушка (PNG чат-пузыря, превью видео), визуально неотличимая от реального виджета. Только при интеракции - клик или программный сигнал намерения (mouseenter с dwell) - грузится реальный третьесторонний payload. Пакет @next/third-parties поставляет facade для YouTube, Google Maps, GTM, Intercom и Zendesk из коробки.
Partytown (Builder.io, 2022) решает задачу иначе: переселяет скрипты в выделенный Web Worker, синхронно проксируя доступ к DOM через SharedArrayBuffer и service worker. Воркер крутится на фоновом потоке, и 200мс callback аналитики больше не вытесняет кадр анимации на main thread. Компромисс - совместимость: скрипты, полагающиеся на синхронный document.write или браузер-специфичные глобалы, могут ломаться. Partytown - правильный ответ для GA, HubSpot, Hotjar и Meta Pixel; для остального - facade.
Столп 7 - HTTP-кеширование: stale-while-revalidate и контракт свежести
HTTP-кеш - самый недоиспользуемый рычаг производительности в индустрии. Ментальная модель большинства разработчиков - «выстави Cache-Control и надейся» - упускает контракт, который задаёт заголовок. Две директивы, делающие тяжёлую работу в 2026, - immutable и stale-while-revalidate.
Для хешированных статических ассетов (/static/chunk-8a7b3f.js) правильный заголовок - Cache-Control: public, max-age=31536000, immutable. Директива immutable (RFC 8246) явно сообщает браузеру, что ресурс по этому URL не изменится никогда - это отключает ревалидацию даже при ручном reload. Хешированное имя - ключ кеша; смена контента даёт новый хеш и, значит, новый URL. Вместе с длинным edge-кешем это полностью убирает избыточную сетевую работу.
Для нехешированных ресурсов - HTML-страниц, API-ответов, навигационных документов - корректный инструмент это stale-while-revalidate (RFC 5861). Cache-Control: public, max-age=60, stale-while-revalidate=86400 означает: отдавать из кеша 60 секунд; после этого - отдавать *устаревшую* копию мгновенно и асинхронно ревалидировать у origin. Пользователь видит кеш-быстрый ответ каждый раз, кеш сам восстанавливается на горизонте 24 часов. Next.js встраивает это в ISR: edge отдаёт stale, origin перерендеривает, следующий запрос получает свежее.
Столп 8 - Resource Hints: 103 Early Hints и preload scanner
Когда пользователь навигирует на страницу, сервер должен сначала сгенерировать HTML-ответ, прежде чем браузер увидит теги <link rel="preload">. На динамической странице с 300мс серверного думания это 300мс сетевого простоя. HTTP-статус 1xx Early Hints (RFC 8297) и конкретно 103 позволяют серверу отдать preload-заголовки *до* финального 200 - накладывая серверную работу на сетевую доставку критических ресурсов.
Поддержка сильная: Chromium 103+, Vercel, Cloudflare, Fastly. Next.js эмитит 103 автоматически для шрифтов и CSS. Измеримый эффект по данным Shopify 2022: 20–30% снижение LCP на холодных навигациях, где доминирует серверное думание.
<link rel="preconnect"> для критичных third-party origins прогревает DNS, TCP и TLS до того, как браузер попросит ресурс. Применяйте максимум к 4–6 origin (handshake сам дорогой); для менее критичных - только rel="dns-prefetch".
Столп 9 - Edge Computing: топология рендеринга и cold starts
Место рендеринга - архитектурное решение. Client-side выбрасывает вычисления на устройство - дёшево серверу, дорого батарее и LCP. Origin SSR централизует вычисления - дёшево клиенту, дорого по задержке, если origin в us-east-1, а пользователь в Сингапуре. Edge SSR (Vercel Edge Functions, Cloudflare Workers, Deno Deploy) рендерит в точке присутствия в ~50мс от пользователя - получая и маленький бандл SSR-вывода, и низкую задержку близкого origin.
Архитектурная ловушка - cold start. Традиционная Node.js Lambda стартует 300–800мс; V8 isolates (runtime Cloudflare Workers) - <5мс, потому что изоляты шарят один V8-процесс и поднимают только новый контекст выполнения. В 2026 Vercel Fluid Compute переиспользует инстансы функции между конкурентными запросами, амортизируя cold-start по всему жизненному циклу функции. Практическое правило: логика на каждый запрос (auth, A/B-флаги, гео-роутинг) - в isolate-runtime; долгие или тяжёлые по памяти задачи (image processing, ML-инференс) - Node.js Fluid Compute с 300с таймаутом.
Столп 10 - Core Web Vitals: формальные определения и диагностика
Core Web Vitals - не эстетические цели, а формально специфицированные метрики с протокольной семантикой наблюдения от W3C Web Performance Working Group. Все три рапортуются на 75-м перцентиле полевого трафика - то есть вы проходите, только если три четверти пользователей видят хороший опыт, не медиана.
LCP (Largest Contentful Paint) - время рендера самого большого изображения или текстового блока во viewport, относительно navigation start. Порог: <2,5с хорошо, >4,0с плохо. Элемент идентифицируется через PerformanceObserver с entryType largest-contentful-paint. LCP раскладывается на четыре подкомпонента: TTFB, resource load delay, resource load duration, element render delay. Оптимизация должна целиться в доминирующий подкомпонент - preload изображения, не ограниченного шириной канала, это пустая трата.
INP (Interaction to Next Paint) заменил First Input Delay 12 марта 2024. В отличие от FID, измерявшего только задержку на первом взаимодействии, INP наблюдает каждую квалифицирующуюся интеракцию за сессию и рапортует приблизительно 98-й перцентиль. Порог: <200мс хорошо, >500мс плохо. Каждая интеракция раскладывается на input delay (время в очереди обработчика), processing duration (обработчик и работа рендера), presentation delay (от композитора до экрана). Детальный разбор INP - в статье Оптимизация INP в Next.js 16.
CLS (Cumulative Layout Shift) - безразмерное произведение impact fraction (доля viewport, затронутая сдвигом) и distance fraction (насколько далеко сдвинулись элементы). Порог: <0,1 хорошо, >0,25 плохо. Окно измерения - «session windows» (до 5с между сдвигами, лимит 5с суммарно), не весь жизненный цикл страницы - критичный нюанс, предотвращающий бесконечное накопление в долгоживущем SPA. Причины: поздние шрифты без size-adjust, изображения без width/height, реклама/embed без резерва, клиентский инжект контента над сгибом.
Столп 11 - Видео и медиа: инженерия кодеков и адаптивный стриминг
Автоплей фонового видео - обманчиво дорогая фича. 10-секундный 1080p H.264 loop весит ~3 МБ; тот же loop в AV1 (ffmpeg -c:v libaom-av1 -crf 30 -b:v 0) - ~900 КБ, в 3,3 раза меньше при том же визуальном качестве. AV1 - правильный baseline в 2026; аппаратный декод на Android и iPhone 15+ делает вопрос софт-декода неактуальным.
GIF - антипаттерн при любом размере выше ~50 КБ: формат 1987 года с палитровой индексацией и без межкадрового сжатия. 3 МБ GIF тривиально становится 200 КБ muted-looping MP4. Паттерн <video autoplay muted loop playsinline> сохраняет «просто играет» от GIF при в 10–20 раз лучшем сжатии. Атрибут playsinline критичен для iOS: без него видео открывается fullscreen по тапу.
Для длинного видео (hero loops >30с, демо, отзывы) обязателен HLS или DASH адаптивный стриминг. HLS режет видео на чанки по 2–6с на нескольких лестницах битрейта; плеер выбирает лестницу по измеренному throughput и апгрейдит/даунгрейдит по ходу. Мобильный 4G тянет 480p, десктоп на оптике апгрейдится до 1080p в процессе. Без адаптивного стриминга мобильный пользователь платит полную цену 1080p, а браузер троттлит соединение.
Столп 12 - Real User Monitoring: реальность полевых данных
Лабораторные инструменты - Lighthouse, PageSpeed Insights, WebPageTest - показывают то, что синтетическое устройство на синтетическом соединении измерило. Это диагностические инструменты, не ранжирующие. Сигнал Google и датасет HTTP Archive построены на полевых данных: Chrome User Experience Report (CrUX) агрегирует анонимизированные замеры от opted-in Chrome-пользователей на 75-м перцентиле за 28-дневное окно.
Архитектурное следствие: важен 75-й перцентиль *ваших реальных пользователей*, не 50-й перцентиль Lighthouse на вашем MacBook M3. Lighthouse и поле расходятся в 3–5 раз для приложений с тяжёлыми клиентскими вычислениями, медленными third-party или длинными анимациями. Единственный способ закрыть разрыв - инструментировать продакшн клиентским web-vitals (Google, MIT), форвардить каждое измерение в RUM-бэкенд (Vercel Speed Insights, DataDog RUM, SpeedCurve, Cloudflare Web Analytics) и сегментировать распределение по шаблону страницы, классу устройства, гео и типу соединения.
Библиотека web-vitals - ~2 КБ gzipped, использует PerformanceObserver для отчёта по LCP, INP, CLS, FCP, TTFB без семплирования. Форвардьте асинхронно через navigator.sendBeacon, чтобы само измерение не попало в INP. Как только пайплайн живой - задайте бюджет на шаблон страницы (LCP_p75 <= 2000ms, INP_p75 <= 150ms, CLS_p75 <= 0.05) и относитесь к его нарушению как к регрессии, блокирующей билд.
Кейс: архитектура Three.js hero на этом портфолио
Этот портфолио несёт реалтайм Three.js background - архитектурно это самый дорогой класс виджетов в вебе: WebGL-контекст, сцен-граф, 60Hz render loop и обычно 400–800 КБ сжатого бандла. Синхронная загрузка как hero уничтожила бы LCP. Реализация использует три наложенных техники.
- Poster-first рендер: hero-кадр - предрендеренный 1200×800 AVIF-постер, именно он LCP-элемент. Three.js-runtime никогда не на критическом пути.
- Инициализация по намерению: импорты Three.js и сцен-граф загружаются за
requestIdleCallbackи первый сигнал намерения (скролл, hover, 2-секундный таймер - что раньше). INP в окне первого paint сохранён. - Контейнмент и отсрочка нижних блоков: контактная форма, карусель цен, слайдер отзывов - через
next/dynamic({ ssr: false })с suspense-заглушками, резервирующими CLS-безопасные размеры.
Если на вашем интернет-магазине или маркетинг-сайте есть похожее структурное ограничение - тяжёлый hero-виджет, медленный TTFB, упорная регрессия INP - начните с услуги performance-архитектуры, посмотрите кейсы или обсудите задачу напрямую.
Заключение
Веб-производительность в 2026 - это не оптимизация напоследок. Это результат двенадцати архитектурных решений: протокол, конвейер изображений, доставка шрифтов, гидратация, rendering path, third-party скрипты, кеширование, resource hints, edge-топология, диагностика vitals, медиа-кодеки и RUM. Пропустил один столп - получи 30–60% регрессии именно на той метрике, которую этот столп держит, а не скромные 5%. Внедри все двенадцать правильно - и сайт окажется в верхнем дециле веба. Это напрямую конвертируется в вовлечение, удержание пользователей и выручку.
Источники
- HTTP Archive 2025 Web Almanac, State of the Web Report: https://almanac.httparchive.org
- W3C Web Performance Working Group, спецификации Core Web Vitals: https://www.w3.org/webperf/
- Chrome User Experience Report (CrUX): https://developer.chrome.com/docs/crux
- RFC 9114 - HTTP/3 (IETF, 2022)
- RFC 9000 - QUIC Transport Protocol (IETF, 2021)
- RFC 8246 - HTTP Immutable Responses (IETF, 2017)
- RFC 5861 - stale-while-revalidate / stale-if-error (IETF, 2010)
- RFC 8297 - HTTP 103 Early Hints (IETF, 2017)
- Deloitte Digital, *Milliseconds Make Millions* (2020) - бенчмарк влияния на конверсию
- Osmani, A. *The Cost of JavaScript*, Google Chrome DevRel (2022, обновлено 2025)
- Google Search Central, анонс Page Experience и INP (май 2023): https://developers.google.com/search/blog/2023/05/introducing-inp
- W3C CSS Containment Module Level 2, Editor's Draft
- web-vitals JavaScript library, Google / MIT: https://github.com/GoogleChrome/web-vitals
