DarkRiDDeR15 мин

Waterfall без иллюзий: как понять, какой ресурс блокирует страницу

FrontendПроизводительность

В waterfall длинная полоса у шрифта выглядит как причина медленной страницы, но шрифт мог начать загрузку после того, как главный контент уже отрисовался. Цена ошибки — менять порядок ресурсов без понимания зависимости, получить вспышку нестилизованного текста и увеличить количество сетевых запросов.

Механика здесь простая: ресурс задерживает страницу только через конкретную зависимость — parser, CSSOM, layout, script или компонент, который ждёт ответ. Воспроизводимый пример покажет, как собрать записи ресурсов в браузере и отфильтровать их по времени старта и статусу блокировки.

Полоса времени не объясняет причинность

Waterfall отвечает на вопрос «когда ресурс был активен». Он не отвечает на вопрос «почему экран ждал». Для причинности нужны DOM-порядок, атрибуты rel и async/defer, тип ресурса и событие, которое использовало результат. Две полосы одинаковой длины могут иметь разную цену: одна относится к hero-изображению, другая — к аналитике после загрузки.

Читаем запись ресурса
ПризнакЧто он даётСледующая проверка
nameкакой URL запрошенсекреты и query удалены?
initiatorTypeкто начал запросparser, script, css или fetch
startTimeкогда началась активностькакое событие было раньше
responseEndкогда пришёл ответкто действительно ждал
renderBlockingStatusпризнак блокировкиподдерживает ли его браузер
Матрица проверки ресурса: инициатор, время, статус блокировки, потребитель и действие.
Матрица не сортирует ресурсы по размеру. Она связывает строку waterfall с проверяемой зависимостью страницы.

Учебная страница с наблюдателем

Это локальный HTML-фрагмент. Вставьте его в страницу, запущенную любым статическим сервером, откройте DevTools и посмотрите консоль. Входом являются browser PerformanceEntry. Результат — компактный список ресурсов, которые начали работу до события DOMContentLoaded, с указанием инициатора. Если браузер не поддерживает поле, выводится unknown, а не придуманное значение.

const started = performance.getEntriesByType('resource').map((entry) => ({
  name: new URL(entry.name).pathname,
  initiator: entry.initiatorType,
  start: Math.round(entry.startTime),
  end: Math.round(entry.responseEnd),
  blocking: entry.renderBlockingStatus ?? 'unknown',
}));

const navigation = performance.getEntriesByType('navigation')[0];
const domEnd = navigation?.domContentLoadedEventEnd ?? Infinity;
console.table(started.filter((entry) => entry.start < domEnd));

Поля start и end — миллисекунды относительно начала навигации. Фильтр оставляет ресурсы, начавшие работу до DOMContentLoaded, но это ещё не список причин. Для каждой строки проверьте потребителя: script мог запросить JSON после DOM, а CSS мог повлиять на первый layout. Наблюдатель сокращает поиск, а не заменяет чтение кода.

Как CSS и script меняют порядок

CSS, подключённый в head, нужен браузеру для построения стилей и поэтому часто попадает в раннюю часть цепочки. Обычный script без атрибута может остановить parser. defer загружает скрипт параллельно и исполняет его после разбора документа в порядке появления. async отдаёт порядок загрузке и подходит только для независимого кода.

Любое изменение атрибута проверяется через зависимость. Если script обращается к DOM-узлу, defer обычно подходит. Если он ожидает библиотеку выше, async может дать race. Если стиль нужен только внутри раскрываемого блока, его можно разделить, но нужно проверить скачок layout и доступность. Критерий решения — не «полоса стала короче», а перестал ли нужный компонент ждать ресурс.

Размер и приоритет не одно и то же

Большой ресурс может загружаться низким приоритетом после первого экрана, а маленький CSS блокировать парсер. Проверяйте размер вместе с инициатором, типом, приоритетом и моментом потребления. В Resource Timing есть данные о времени и размерах, но доступ к кросс-доменным значениям зависит от заголовков и политики браузера.

Три изменения и их риск
ИзменениеПоложительный эффектЧто сломать легко
defer для независимого scriptparser не ждёт выполнениекод, рассчитывающий на ранний глобальный объект
preload для критичного ресурсазапрос начинается раньшелишний запрос и неправильный тип
разделение CSSменьше раннего CSSFOUC, layout shift и порядок правил

Порядок чтения waterfall

  1. Зафиксировать страницу, режим кэша, viewport и браузер, иначе строки несопоставимы.
  2. Отфильтровать resource entries по initiatorType и времени до DOMContentLoaded.
  3. Для каждой ранней строки найти тег, script или CSS-правило, которое её инициировало.
  4. Проверить, кто читает ответ и действительно ли этот потребитель влияет на первый экран.
  5. Изменить один атрибут или порядок загрузки и повторить запись.
  6. Сравнить визуальный результат, long tasks и сетевой waterfall; не принимать сокращение одной полосы как успех.

Ограничения и следующий шаг

PerformanceObserver и renderBlockingStatus зависят от поддержки браузера. Сетевые записи скрывают часть кросс-доменных данных, а локальная страница не отражает задержки мобильной сети. Визуальный результат зависит от шрифтов, viewport и устройства. Поэтому наблюдатель полезен как диагностический слой, но не как единственный критерий качества.

Следующий шаг — сохранить в тестовом отчёте таблицу «ресурс → инициатор → потребитель → действие». В ней должны быть хотя бы один оставленный ресурс и один ресурс, который вы сознательно отложили. Это помогает объяснить компромисс и не возвращать preload или async при следующем изменении шаблона.

Проверяемые источники

  • W3C Navigation Timing Level 2 — Working Draft, 25 February 2026. Определяет навигационные временные метки и связь между этапами загрузки документа. Граница применимости: Рабочий черновик стандарта не сообщает значения метрик вашего браузера и не заменяет полевой сбор.
  • W3C Performance Timeline — Candidate Recommendation Draft, 21 May 2025. Определяет PerformanceEntry, getEntries и PerformanceObserver как основу чтения метрик. Граница применимости: Спецификация не задаёт пороги качества и не гарантирует одинаковый набор entry во всех движках.
  • W3C Resource Timing — Candidate Recommendation Draft, 20 April 2026. Определяет записи ресурсов, размеры и render-blocking status для сетевого слоя страницы. Граница применимости: Не описывает серверную архитектуру, пользовательский опыт целиком или причинность конкретной задержки.