HTML

Что такое Critical Rendering Path?

Critical Rendering Path

Critical Rendering Path (CRP) - это последовательность шагов, которые браузер проходит от момента получения HTML до отрисовки первых пикселей на экране. Понимание этого пути нужно, чтобы знать, что тормозит первую отрисовку (First Paint / First Contentful Paint), и как её ускорить.

Браузер не ждёт весь HTML целиком - он обрабатывает поток данных по мере загрузки. Но чтобы что-то показать, ему нужны две модели: дерево содержимого (DOM) и дерево стилей (CSSOM). Их он объединяет и только потом считает геометрию и рисует.

HTML → DOM CSS → CSSOM Render Tree этап Style Layout Paint Composite

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>

Источники