Critical Rendering Path
Critical Rendering Path (CRP) - это последовательность шагов, которые браузер проходит от момента получения HTML до отрисовки первых пикселей на экране. Понимание этого пути нужно, чтобы знать, что тормозит первую отрисовку (First Paint / First Contentful Paint), и как её ускорить.
Браузер не ждёт весь HTML целиком - он обрабатывает поток данных по мере загрузки. Но чтобы что-то показать, ему нужны две модели: дерево содержимого (DOM) и дерево стилей (CSSOM). Их он объединяет и только потом считает геометрию и рисует.
DOM и CSSOM строятся параллельно, объединяются в Render Tree (это же - шаг Style, recalculate style: сопоставление правил CSSOM с узлами DOM), а дальше идут Layout → Paint → Composite.
1. HTML → DOM
Браузер читает HTML побайтово: байты → символы → токены → узлы → DOM-дерево. DOM строится инкрементально, поэтому браузер может начать работу, не дожидаясь конца документа.
Важный нюанс: обычный <script> блокирует парсинг. Встретив его, браузер останавливает построение DOM, скачивает и выполняет скрипт (потому что JS может менять DOM через document.write), и только потом продолжает. Поэтому скрипты ставят в конец <body> или помечают defer/async.
Как с этим в 2026.
defer/asyncне устарели - это всё ещё базовые примитивы. Но руками их почти не пишут: ES-модули (<script type="module">) ведут себя какdeferпо умолчанию, а сборщики и фреймворки (Next.js, Vite) сами делают code-splitting и неблокирующую загрузку. В Next естьnext/scriptсо стратегиями (afterInteractive,lazyOnload), а лучший приём - вообще не отправлять лишний JS на клиент через Server Components и streaming SSR.
2. CSS → CSSOM
Параллельно браузер загружает и парсит CSS в CSSOM - дерево, где для каждого узла вычислены итоговые стили (с учётом каскада и наследования).
CSS - render-blocking ресурс: браузер не отрисует ничего, пока не построит полный CSSOM. Логика проста - показать страницу с ещё не применёнными стилями значило бы мигнуть «голым» HTML, а затем перерисовать. Поэтому объём и скорость загрузки CSS напрямую влияют на время первой отрисовки.
К тому же CSS может блокировать выполнение скриптов: если перед <script> идёт <link rel="stylesheet">, скрипт дождётся построения CSSOM (вдруг ему понадобится читать стили).
3. DOM + CSSOM → Render Tree
Браузер объединяет два дерева в Render Tree - оставляет только то, что реально видно и будет нарисовано. Сюда не попадают:
- узлы вне отображения -
<head>,<meta>,<script>; - элементы с
display: none(полностью исключены).
Важно: visibility: hidden и opacity: 0 остаются в Render Tree - они занимают место и участвуют в раскладке, просто невидимы.
4. Layout (Reflow)
По Render Tree браузер вычисляет геометрию - точные размеры и координаты каждого элемента в viewport (учитывая box-model, проценты, флексы, гриды). Этот шаг ещё называют reflow.
Layout пересчитывается при изменении размеров/позиций: ресайз окна, добавление элементов, смена шрифта, чтение «layout-свойств» (offsetHeight, getBoundingClientRect) сразу после записи в DOM - это вызывает принудительный синхронный reflow и тормозит.
5. Paint
Браузер заполняет пиксели: текст, цвета, фоны, границы, тени, изображения. На сложных страницах рисование разбивается на слои (layers).
6. Composite
Слои объединяются (как правило, на GPU) и выводятся на экран. Ключевой момент для анимаций: свойства transform и opacity обрабатываются только на этапе Composite - без Layout и Paint. Поэтому анимировать стоит именно их (а не top/left/width) - это самый дешёвый и плавный путь.
Как ускорить CRP
Цель - минимизировать число и объём render-blocking ресурсов и не задерживать парсинг.
<!-- Критический CSS первого экрана - инлайном в <head> -->
<style>/* минимальные стили для того, что видно сразу */</style>
<!-- Остальной CSS грузим неблокирующе -->
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
<!-- Скрипты не блокируют парсинг -->
<script defer src="app.js"></script>
База:
- Критический CSS инлайнить, остальной грузить асинхронно - чтобы быстрее построить CSSOM.
- JS через
defer/async, чтобы не блокировать построение DOM. - Уменьшать объём CSS/JS (tree-shaking, minify, удаление неиспользуемого).
- В рантайме - не вызывать принудительный reflow в циклах (сначала читать layout-свойства, потом писать).
Современные рычаги
Поверх базовых приёмов браузеры дают более точные инструменты управления загрузкой ресурсов:
preconnect / dns-prefetch - заранее установить соединение со сторонним доменом (CDN, шрифты, API), чтобы к моменту запроса не тратить время на DNS + TLS-рукопожатие.
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
preload - сказать браузеру «этот ресурс точно нужен рано, скачай его в высоком приоритете». Чаще всего для шрифтов и критичных изображений (LCP).
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
modulepreload - то же, но для ES-модулей: браузер не только скачает, но и распарсит/скомпилирует модуль и его зависимости заранее.
<link rel="modulepreload" href="/app.js">
Priority Hints (fetchpriority) - вручную поднять или понизить приоритет конкретного ресурса. Классика - повысить приоритет LCP-картинки и понизить у второстепенных.
<img src="/hero.webp" fetchpriority="high" alt="">
<img src="/footer-logo.png" fetchpriority="low" alt="">
103 Early Hints - ответ сервера ещё до основного HTML, в котором он подсказывает браузеру начать грузить критичные ресурсы (Link: rel=preload). Браузер качает CSS/шрифты, пока сервер готовит страницу.
Speculation Rules API - предзагрузка (prefetch) и даже полный предрендер (prerender) следующих страниц по вероятным переходам. Переход тогда ощущается мгновенным - CRP следующей страницы уже пройден заранее.
<script type="speculationrules">
{ "prerender": [{ "where": { "href_matches": "/theory/*" } }] }
</script>