Приоритизация загрузки ресурсов в 2026: fetchpriority, preload, preconnect и 103 Early Hints

Практическое руководство по управлению приоритетами загрузки ресурсов. Разбираем fetchpriority, preload, preconnect и 103 Early Hints — с примерами кода, таблицами приоритетов и пошаговой стратегией оптимизации LCP.

Введение: почему приоритизация ресурсов решает судьбу вашего LCP

Каждый раз, когда пользователь открывает веб-страницу, браузер сталкивается с десятками (а порой и сотнями) ресурсов, которые нужно загрузить: стили, скрипты, шрифты, изображения, iframe-ы, данные API. Но пропускная способность сети ограничена, а количество одновременных TCP-соединений к одному домену — не больше шести. Браузеру приходится решать, что загружать первым, а что подождёт. И именно эта внутренняя система приоритизации определяет, увидит ли пользователь контент через 1,2 секунды или через 4,5.

Метрики Core Web Vitals — и в особенности Largest Contentful Paint (LCP) — напрямую зависят от того, насколько быстро браузер получит критические ресурсы. LCP измеряет время отрисовки самого крупного видимого элемента: hero-изображения, заголовка с кастомным шрифтом или фонового видео. Если браузер загружает этот ресурс с низким приоритетом, а карусель рекламных баннеров внизу страницы — с высоким, ваш LCP неизбежно пострадает.

Честно говоря, понимание и контроль приоритетов загрузки — это одна из самых эффективных, но при этом часто упускаемых из виду стратегий оптимизации. В 2026 году у нас есть мощный набор инструментов: атрибут fetchpriority, подсказки ресурсов (preload, preconnect, prefetch, dns-prefetch) и HTTP-статус 103 Early Hints. Давайте разберём каждый из них — с практическими примерами и чёткой стратегией.

Как браузеры определяют приоритет ресурсов

Preload Scanner и его роль

Когда браузер получает HTML-документ, основной парсер начинает последовательно обрабатывать разметку. Но ещё до завершения парсинга в дело вступает preload scanner — вторичный лёгкий парсер, который «забегает вперёд» и обнаруживает ресурсы в HTML: теги <link>, <script>, <img> и так далее. Благодаря этому браузер начинает сетевые запросы раньше, не дожидаясь, пока основной парсер дойдёт до нужного элемента.

Preload scanner — одна из причин, почему ресурсы, объявленные прямо в HTML, загружаются быстрее, чем добавленные динамически через JavaScript. Если ваш LCP-элемент — изображение, вставляемое JS-фреймворком на стороне клиента, preload scanner просто не увидит его, пока скрипт не выполнится. Это важный момент, о котором многие забывают.

Приоритеты по умолчанию для различных типов ресурсов

Chromium-браузеры присваивают каждому ресурсу один из пяти внутренних приоритетов: Highest, High, Medium, Low, Lowest. Вот как они распределяются по умолчанию:

Тип ресурса Приоритет по умолчанию Примечания
CSS (в <head>) Highest Блокирует рендеринг
Шрифты (с @font-face) High Обнаруживаются поздно, при парсинге CSS
Синхронные скрипты в <head> High Блокируют парсинг HTML
Синхронные скрипты после первого изображения Medium Понижается в Chrome после обнаружения изображений
async/defer скрипты Low Не блокируют парсинг
Изображения (видимые в viewport) Medium (позже повышается до High) Chrome повышает приоритет после layout
Изображения (за пределами viewport) Low Могут быть отложены lazy loading
<link rel="preload"> Зависит от as Наследует приоритет типа ресурса
<link rel="prefetch"> Lowest Для будущей навигации
Fetch/XHR High Данные API обычно критичны
<iframe> Low Обычно не критичны для основного контента

Как работает очередь приоритетов в Chrome

Chrome использует двухуровневую систему планирования сетевых запросов. На первом уровне каждый ресурс получает приоритет на основе типа и расположения в документе. На втором уровне, после выполнения layout, Chrome переоценивает приоритеты изображений: те, что оказались в viewport, получают повышение.

И вот тут начинается самое интересное. Браузер не может заранее знать, какое именно изображение станет LCP-элементом. Он присваивает начальный приоритет на основе эвристик и обновляет его позже. Но к этому моменту может быть потрачено драгоценное время. Именно здесь вступают в игру инструменты ручного управления приоритетами.

Fetch Priority API: атрибут fetchpriority

Основы синтаксиса

Атрибут fetchpriority позволяет разработчику явно сказать браузеру, какой приоритет присвоить загрузке конкретного ресурса. Он принимает три значения:

  • fetchpriority="high" — повысить приоритет относительно значения по умолчанию
  • fetchpriority="low" — понизить приоритет
  • fetchpriority="auto" — использовать приоритет по умолчанию (если атрибут не указан)

Атрибут работает на элементах <img>, <link>, <script> и <iframe>, а также в Fetch API через свойство priority.

Практический пример: LCP hero-изображение

Самый распространённый и, пожалуй, самый эффективный кейс — повышение приоритета LCP-изображения. По умолчанию браузер начинает загрузку изображений с приоритетом Low и повышает его только после layout. С fetchpriority="high" изображение начнёт загружаться с максимальным приоритетом сразу:

<!-- Hero-изображение, являющееся LCP-элементом -->
<img
  src="/images/hero-banner.webp"
  alt="Главный баннер акции"
  width="1200"
  height="600"
  fetchpriority="high"
>

<!-- ВАЖНО: не используйте loading="lazy" на LCP-изображении! -->
<!-- Это две противоположные инструкции -->

По данным исследований Google, добавление fetchpriority="high" на LCP-изображение может улучшить LCP на 200–500 мс — в зависимости от сложности страницы и количества конкурирующих ресурсов. На практике я видел даже более заметные улучшения на тяжёлых e-commerce страницах.

Понижение приоритета для изображений за пределами viewport

Если на странице в видимой области несколько изображений (например, карусель), но только одно из них — LCP-элемент, стоит понизить приоритет остальных:

<!-- LCP-элемент -->
<img
  src="/images/hero.webp"
  alt="Основной баннер"
  fetchpriority="high"
  width="1200"
  height="600"
>

<!-- Второстепенные изображения в видимой области -->
<img
  src="/images/promo-1.webp"
  alt="Промо 1"
  fetchpriority="low"
  width="400"
  height="300"
>

<img
  src="/images/promo-2.webp"
  alt="Промо 2"
  fetchpriority="low"
  width="400"
  height="300"
>

<!-- Изображения ниже fold — используйте lazy loading -->
<img
  src="/images/product-card.webp"
  alt="Карточка товара"
  loading="lazy"
  width="300"
  height="300"
>

fetchpriority с link rel="preload"

Атрибут fetchpriority особенно полезен в сочетании с <link rel="preload">, когда вы предзагружаете несколько ресурсов и хотите указать их относительный приоритет:

<head>
  <!-- Критический шрифт — высокий приоритет -->
  <link
    rel="preload"
    href="/fonts/main-text.woff2"
    as="font"
    type="font/woff2"
    crossorigin
    fetchpriority="high"
  >

  <!-- LCP-изображение — высокий приоритет -->
  <link
    rel="preload"
    href="/images/hero.webp"
    as="image"
    fetchpriority="high"
  >

  <!-- Декоративный шрифт — пониженный приоритет -->
  <link
    rel="preload"
    href="/fonts/decorative.woff2"
    as="font"
    type="font/woff2"
    crossorigin
    fetchpriority="low"
  >
</head>

fetchpriority для скриптов и iframe

Скрипты аналитики, виджеты чата и рекламные iframe постоянно конкурируют с критическими ресурсами за пропускную способность. Используйте fetchpriority="low", чтобы явно снизить их приоритет:

<!-- Аналитика — не критична для пользователя -->
<script
  src="https://analytics.example.com/tracker.js"
  async
  fetchpriority="low"
></script>

<!-- Виджет чата — не нужен при начальной загрузке -->
<iframe
  src="https://chat.example.com/widget"
  fetchpriority="low"
  loading="lazy"
  width="350"
  height="500"
></iframe>

<!-- Критический скрипт приложения -->
<script src="/js/app-critical.js" fetchpriority="high"></script>

Использование fetchpriority в Fetch API

В JavaScript при выполнении fetch-запросов тоже можно указать приоритет:

// Критический API-запрос для данных страницы
const response = await fetch('/api/product/details', {
  priority: 'high'
});

// Некритичный запрос для рекомендаций
const recommendations = await fetch('/api/recommendations', {
  priority: 'low'
});

Типичные ошибки при использовании fetchpriority

Самая частая ошибка — установка fetchpriority="high" на множество ресурсов. Тут работает простая логика: если всё имеет высокий приоритет, то ничто не имеет высокого приоритета. Браузер всё равно будет вынужден упорядочить запросы, и весь эффект исчезнет. Несколько рекомендаций:

  • Используйте fetchpriority="high" максимум на 1–2 ресурсах на странице
  • Не ставьте fetchpriority="high" и loading="lazy" на одном элементе — это взаимоисключающие инструкции
  • Не используйте fetchpriority="high" на ресурсах, которые и так имеют высокий приоритет по умолчанию (CSS в <head>)
  • Не забывайте про fetchpriority="low" — понижение приоритета некритичных ресурсов зачастую даёт больший эффект, чем повышение

Поддержка браузерами

На начало 2026 года поддержка fetchpriority охватывает подавляющее большинство пользователей:

Браузер Версия с поддержкой Дата релиза
Chrome 102+ Май 2022
Edge 102+ Май 2022
Safari 17.2+ Декабрь 2023
Firefox 132+ Октябрь 2024
Opera 88+ Июнь 2022

Что приятно — атрибут является прогрессивным улучшением: браузеры, не поддерживающие его, просто игнорируют атрибут, и ресурс загружается с приоритетом по умолчанию. Риска поломки нет, так что можно внедрять без опасений.

Подсказки ресурсов: preload, prefetch, preconnect, dns-prefetch

link rel="preload" — предварительная загрузка критических ресурсов

Директива preload указывает браузеру, что ресурс точно понадобится на текущей странице и его нужно загрузить как можно раньше. Это особенно полезно для ресурсов, которые браузер обнаруживает поздно: шрифтов (спрятанных в CSS), фоновых изображений, динамически загружаемых скриптов.

<head>
  <!-- Предзагрузка критического шрифта -->
  <link
    rel="preload"
    href="/fonts/inter-var.woff2"
    as="font"
    type="font/woff2"
    crossorigin
  >

  <!-- Предзагрузка LCP-изображения -->
  <link
    rel="preload"
    href="/images/hero-banner.webp"
    as="image"
    type="image/webp"
  >

  <!-- Предзагрузка критического CSS -->
  <link
    rel="preload"
    href="/css/critical.css"
    as="style"
  >
  <link rel="stylesheet" href="/css/critical.css">

  <!-- Предзагрузка критического скрипта -->
  <link
    rel="preload"
    href="/js/app-core.js"
    as="script"
  >
</head>

Ключевые правила использования preload:

  • Всегда указывайте атрибут as — без него браузер не определит правильный приоритет и может загрузить ресурс дважды
  • Для шрифтов обязателен crossorigin — даже если шрифт на вашем же домене. Таковы требования спецификации: шрифты всегда запрашиваются в режиме CORS
  • Не больше 2–3 preload — каждый из них конкурирует с остальными за пропускную способность
  • Убедитесь, что preloaded-ресурс реально используется — Chrome выдаст предупреждение в консоли, если ресурс предзагружен, но не использован в течение 3 секунд

Допустимые значения атрибута as:

Значение as Тип ресурса Приоритет
style CSS-файлы Highest
script JavaScript High
font Веб-шрифты High
image Изображения Low (повышается)
fetch Данные API High
document HTML-документ High
video Видеофайлы Low
audio Аудиофайлы Low

Preload для адаптивных изображений с imagesrcset

Если ваш LCP-элемент — адаптивное изображение с srcset, используйте атрибуты imagesrcset и imagesizes в теге preload:

<link
  rel="preload"
  as="image"
  imagesrcset="
    /images/hero-400.webp 400w,
    /images/hero-800.webp 800w,
    /images/hero-1200.webp 1200w
  "
  imagesizes="(max-width: 600px) 100vw, (max-width: 1024px) 80vw, 1200px"
  fetchpriority="high"
>

link rel="prefetch" — спекулятивная загрузка для будущей навигации

В отличие от preload, директива prefetch сообщает браузеру, что ресурс может понадобиться при следующей навигации. Браузер загружает его с наименьшим приоритетом в моменты простоя сети:

<!-- Предварительная загрузка страницы, на которую пользователь
     вероятнее всего перейдёт -->
<link rel="prefetch" href="/catalog/popular-products">

<!-- Предварительная загрузка JS-бандла следующей страницы -->
<link rel="prefetch" href="/js/product-page-bundle.js" as="script">

<!-- Предварительная загрузка CSS следующей страницы -->
<link rel="prefetch" href="/css/product-page.css" as="style">

Когда использовать prefetch:

  • На страницах со списками товаров — для предзагрузки наиболее вероятных кликов
  • В многошаговых формах — для ресурсов следующего шага
  • На посадочных страницах — для предзагрузки основного раздела

Только не злоупотребляйте prefetch — он расходует трафик пользователя. На мобильных устройствах с лимитированным трафиком браузеры могут вообще игнорировать prefetch при включённом режиме экономии данных.

link rel="preconnect" — раннее подключение к сторонним серверам

Директива preconnect говорит браузеру заранее установить соединение с указанным сервером, включая DNS-резолвинг, TCP-рукопожатие и TLS-согласование. Это может сэкономить 100–500 мс при первом запросе к стороннему домену — а на медленных мобильных сетях экономия бывает ещё ощутимее.

<head>
  <!-- Google Fonts — всегда два домена -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

  <!-- CDN с изображениями -->
  <link rel="preconnect" href="https://cdn.example.com">

  <!-- API сервер -->
  <link rel="preconnect" href="https://api.example.com" crossorigin>

  <!-- Аналитика — лучше dns-prefetch, так как менее критична -->
  <link rel="dns-prefetch" href="https://analytics.example.com">
</head>

Рекомендации по preconnect:

  • Максимум 2–4 домена — каждое соединение съедает ресурсы CPU и сети
  • Применяйте только для доменов, к которым запрос будет в первые 10 секунд загрузки — неиспользованные соединения закрываются автоматически
  • Указывайте crossorigin для доменов с CORS-ресурсами (шрифты, fetch-запросы)
  • Размещайте <link rel="preconnect"> как можно раньше в <head> — до блокирующих CSS и JS

link rel="dns-prefetch" — облегчённая альтернатива

Если полный preconnect избыточен, можно ограничиться dns-prefetch — он выполняет только DNS-резолвинг. Подходит для доменов, к которым обращение будет, но не в первые секунды:

<!-- Менее критичные сторонние сервисы -->
<link rel="dns-prefetch" href="https://analytics.example.com">
<link rel="dns-prefetch" href="https://ads.example.com">
<link rel="dns-prefetch" href="https://social-widget.example.com">

<!-- Распространённый паттерн: preconnect + dns-prefetch fallback -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">

Последний паттерн — preconnect с dns-prefetch fallback — хорошая практика для максимальной совместимости. Современные браузеры выполнят полное подключение и проигнорируют dns-prefetch. Старые — хотя бы сделают DNS-резолвинг.

Сравнительная таблица подсказок ресурсов

Подсказка Что делает Приоритет Для текущей страницы? Лимит
preload Загружает ресурс заранее Зависит от as Да 2–3 ресурса
prefetch Загружает для следующей страницы Lowest Нет Без жёсткого лимита
preconnect DNS + TCP + TLS к домену Да 2–4 домена
dns-prefetch Только DNS-резолвинг Да/Нет Без жёсткого лимита

103 Early Hints: оптимизация «времени размышления» сервера

Что такое 103 Early Hints

HTTP-статус 103 Early Hints — это механизм, позволяющий серверу отправить предварительные заголовки (включая подсказки preload и preconnect) до того, как основной ответ будет готов. Звучит просто, но для динамических страниц это настоящая находка — серверу ведь нужно время на бизнес-логику, запросы к базе данных, рендеринг шаблона.

Обычный процесс выглядит так:

  1. Браузер отправляет запрос
  2. Сервер думает 200–800 мс (TTFB)
  3. Сервер отправляет ответ 200 с HTML
  4. Браузер парсит HTML и начинает загружать ресурсы

А с 103 Early Hints:

  1. Браузер отправляет запрос
  2. Сервер мгновенно отправляет ответ 103 с подсказками
  3. Браузер тут же начинает загружать подсказанные ресурсы
  4. Сервер думает свои 200–800 мс
  5. Сервер отправляет финальный ответ 200 с HTML

Суть в том, что браузер использует «мёртвое время» ожидания для параллельной загрузки критических ресурсов. Чем больше TTFB, тем больше выигрыш.

Формат HTTP-ответа

HTTP/1.1 103 Early Hints
Link: </css/critical.css>; rel=preload; as=style
Link: </fonts/inter-var.woff2>; rel=preload; as=font; crossorigin
Link: <https://cdn.example.com>; rel=preconnect

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 45678

<!DOCTYPE html>
<html>
...

Настройка 103 Early Hints в Nginx

Nginx поддерживает 103 Early Hints начиная с версии 1.25.3 через модуль ngx_http_early_hints_module. Вот пример конфигурации:

server {
    listen 443 ssl http2;
    server_name example.com;

    # Включаем поддержку 103 Early Hints
    location / {
        # Отправляем 103 до проксирования к бэкенду
        add_header Link "</css/critical.css>; rel=preload; as=style" early;
        add_header Link "</fonts/inter-var.woff2>; rel=preload; as=font; crossorigin" early;
        add_header Link "<https://cdn.example.com>; rel=preconnect" early;

        proxy_pass http://backend;
        proxy_http_version 1.1;
    }

    # Для статических страниц
    location /landing/ {
        add_header Link "</images/hero-landing.webp>; rel=preload; as=image" early;
        add_header Link "</css/landing.css>; rel=preload; as=style" early;

        try_files $uri $uri/index.html =404;
    }
}

103 Early Hints на Cloudflare

Cloudflare поддерживает 103 Early Hints автоматически. Достаточно включить функцию в панели управления и добавить заголовки Link в ответ origin-сервера. Cloudflare кэширует эти заголовки и отправляет их как 103 Early Hints при последующих запросах:

// В вашем origin-сервере (например, Express.js)
app.get('/', (req, res) => {
    // Cloudflare перехватит эти Link-заголовки
    // и отправит их как 103 Early Hints
    res.set('Link', [
        '</css/main.css>; rel=preload; as=style',
        '</fonts/brand.woff2>; rel=preload; as=font; crossorigin',
        '<https://images.example.com>; rel=preconnect'
    ].join(', '));

    // ... генерация страницы
    res.send(html);
});

Для активации на Cloudflare:

  1. Перейдите в Speed > Optimization > Content Optimization
  2. Включите Early Hints
  3. Убедитесь, что ваш сервер отправляет заголовки Link с rel=preload или rel=preconnect

Настройка в Apache

Apache поддерживает 103 Early Hints через модуль mod_http2:

<VirtualHost *:443>
    ServerName example.com

    # Включаем H2 Early Hints
    H2EarlyHints on

    <Location />
        Header add Link "</css/critical.css>;rel=preload;as=style"
        Header add Link "</fonts/main.woff2>;rel=preload;as=font;crossorigin"
    </Location>
</VirtualHost>

Реальные результаты

По данным Cloudflare, использование 103 Early Hints даёт:

  • Снижение LCP в среднем на 200–500 мс для страниц с высоким TTFB
  • Улучшение FCP на 100–300 мс
  • Наибольший эффект на динамических страницах с серверным рендерингом, где TTFB от 300 мс и выше

Имейте в виду: 103 Early Hints не дадут заметного эффекта для статических страниц с низким TTFB (менее 100 мс) — там просто не из чего выигрывать время.

Практическая стратегия оптимизации: пошаговый план

Шаг 1: Аудит приоритетов с помощью Chrome DevTools

Начните с диагностики. Откройте Chrome DevTools, перейдите на вкладку Network и включите столбец Priority:

  1. Откройте DevTools (F12 или Ctrl+Shift+I)
  2. Перейдите на вкладку Network
  3. Правый клик на заголовке любого столбца
  4. Включите Priority
  5. Перезагрузите страницу с очисткой кэша (Ctrl+Shift+R)
  6. Изучите приоритеты

Обратите внимание на столбец Initiator — он покажет, откуда инициирован запрос. Также полезна вкладка Performance: запишите профиль загрузки и на Waterfall-диаграмме найдите LCP-элемент. Посмотрите, когда началась его загрузка и что загружалось раньше.

Шаг 2: Определите LCP-элемент и установите fetchpriority="high"

Определить LCP-элемент можно несколькими способами:

  • В Lighthouse — раздел «Largest Contentful Paint element»
  • В DevTools Performance — кликните на метку LCP на таймлайне
  • Программно через консоль:
// Определение LCP-элемента через PerformanceObserver
new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP element:', lastEntry.element);
  console.log('LCP time:', lastEntry.startTime, 'ms');
  console.log('LCP URL:', lastEntry.url);
}).observe({ type: 'largest-contentful-paint', buffered: true });

После этого добавьте fetchpriority="high":

<!-- Если LCP — это img -->
<img
  src="/images/hero.webp"
  alt="Hero banner"
  width="1200"
  height="600"
  fetchpriority="high"
>

<!-- Если LCP — это background-image в CSS,
     используйте preload -->
<link
  rel="preload"
  href="/images/hero-bg.webp"
  as="image"
  fetchpriority="high"
>

Шаг 3: Добавьте preconnect для критических сторонних доменов

Проанализируйте Waterfall в DevTools и найдите сторонние домены с первыми запросами. Типичные кандидаты:

<head>
  <!-- Первый элемент после meta charset -->
  <meta charset="utf-8">

  <!-- Preconnect к критическим доменам -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link rel="preconnect" href="https://cdn.example.com">

  <!-- DNS-prefetch для менее критичных -->
  <link rel="dns-prefetch" href="https://analytics.example.com">

  <!-- Далее CSS, preload и остальные ресурсы -->
  <link rel="stylesheet" href="/css/critical.css">
</head>

Шаг 4: Предзагрузите критические шрифты и CSS

Шрифты — одна из самых частых причин задержки LCP. Почему? Они обнаруживаются поздно, только при парсинге CSS. Предзагрузка решает эту проблему на корню:

<head>
  <!-- Preconnect -->
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

  <!-- Preload критического шрифта -->
  <link
    rel="preload"
    href="/fonts/inter-regular.woff2"
    as="font"
    type="font/woff2"
    crossorigin
  >

  <!-- Preload критического CSS, если он загружается с CDN
       или обнаруживается поздно -->
  <link
    rel="preload"
    href="https://cdn.example.com/css/above-fold.css"
    as="style"
  >

  <!-- Основные стили -->
  <link rel="stylesheet" href="https://cdn.example.com/css/above-fold.css">
  <link rel="stylesheet" href="/css/main.css">
</head>

Загружайте не более 2 шрифтов через preload. Variable font — идеальный кандидат, потому что это один файл вместо нескольких начертаний.

Шаг 5: Понизьте приоритет некритичных ресурсов

Вот что многие упускают: часто больший эффект даёт не повышение приоритета LCP-ресурса, а понижение приоритета всего, что ему мешает:

<!-- Аналитика и трекеры -->
<script
  src="/js/analytics.js"
  async
  fetchpriority="low"
></script>

<!-- Некритичные изображения в видимой области -->
<img
  src="/images/sidebar-ad.webp"
  alt="Реклама"
  fetchpriority="low"
  width="300"
  height="250"
>

<!-- Изображения ниже fold -->
<img
  src="/images/section-2-photo.webp"
  alt="Фото"
  loading="lazy"
  width="800"
  height="600"
>

<!-- Некритичные CSS через media-хак -->
<link
  rel="stylesheet"
  href="/css/non-critical.css"
  media="print"
  onload="this.media='all'"
>
<noscript>
  <link rel="stylesheet" href="/css/non-critical.css">
</noscript>

Шаг 6: Рассмотрите 103 Early Hints при высоком TTFB

Если TTFB вашего сервера больше 300 мс (типичная ситуация для SSR, CMS, e-commerce с персонализацией), 103 Early Hints могут дать ощутимый прирост. Составьте список ресурсов, нужных на каждой странице:

# Типичный набор Early Hints для e-commerce сайта
Link: </css/critical.css>; rel=preload; as=style
Link: </fonts/brand-regular.woff2>; rel=preload; as=font; crossorigin
Link: <https://cdn.example.com>; rel=preconnect
Link: <https://api.example.com>; rel=preconnect; crossorigin

Совет: не включайте в Early Hints ресурсы, специфичные для конкретной страницы (скажем, hero-изображение), если нет механизма определения URL до рендеринга. Используйте только общие для всего сайта ресурсы: основной CSS, бренд-шрифт, CDN-домен.

Типичные ошибки и антипаттерны

1. Перегрузка preload-ами

Одна из самых частых ошибок — объявление слишком большого количества <link rel="preload">. Каждый preload создаёт запрос с высоким приоритетом, и все они начинают толкаться локтями в очереди:

<!-- ПЛОХО: слишком много preload -->
<link rel="preload" href="/css/reset.css" as="style">
<link rel="preload" href="/css/variables.css" as="style">
<link rel="preload" href="/css/layout.css" as="style">
<link rel="preload" href="/css/components.css" as="style">
<link rel="preload" href="/css/utilities.css" as="style">
<link rel="preload" href="/fonts/font1.woff2" as="font" crossorigin>
<link rel="preload" href="/fonts/font2.woff2" as="font" crossorigin>
<link rel="preload" href="/fonts/font3.woff2" as="font" crossorigin>
<link rel="preload" href="/images/hero.webp" as="image">
<link rel="preload" href="/images/logo.svg" as="image">

<!-- ХОРОШО: только критические ресурсы -->
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/images/hero.webp" as="image" fetchpriority="high">

2. Preload без атрибута as

Без as браузер не знает тип ресурса, не может определить приоритет и рискует загрузить ресурс дважды:

<!-- ПЛОХО: нет атрибута as -->
<link rel="preload" href="/fonts/main.woff2">

<!-- ХОРОШО: as указан -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

3. Preload ресурсов, которые не используются

Если предзагрузить ресурс, который на странице не применяется, Chrome сообщит об этом в консоли:

The resource /fonts/heading-bold.woff2 was preloaded
using link preload but not used within a few seconds from
the window's load event. Please make sure it has an appropriate
`as` value and it is preloaded intentionally.

Это не просто «мусорное» предупреждение. Такой preload занимает пропускную способность и тормозит загрузку действительно нужных ресурсов.

4. Противоречивые инструкции: fetchpriority="high" + loading="lazy"

<!-- ПЛОХО: противоречивые инструкции -->
<img
  src="/images/hero.webp"
  fetchpriority="high"
  loading="lazy"
  alt="Hero"
>

<!-- ХОРОШО: для LCP-изображения -->
<img
  src="/images/hero.webp"
  fetchpriority="high"
  alt="Hero"
  width="1200"
  height="600"
>

loading="lazy" говорит «подожди с загрузкой», а fetchpriority="high" — «грузи немедленно». Браузер вынужден выбирать, и результат может быть непредсказуемым. Не ставьте их вместе.

5. Конкурирующие высокие приоритеты

Если fetchpriority="high" стоит на 5 изображениях, 3 скриптах и 2 шрифтах — толку будет ноль. Приоритизация работает относительно: повысить всё — значит не повысить ничего.

6. Preconnect к слишком большому числу доменов

Каждое соединение — это DNS-запрос, TCP-рукопожатие и TLS-согласование. Десять preconnect создадут нагрузку в самый критический момент загрузки:

<!-- ПЛОХО: слишком много preconnect -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.example.com">
<link rel="preconnect" href="https://api.example.com">
<link rel="preconnect" href="https://analytics.example.com">
<link rel="preconnect" href="https://ads.example.com">
<link rel="preconnect" href="https://tracking.example.com">
<link rel="preconnect" href="https://social.example.com">

<!-- ХОРОШО: только критичные домены -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://analytics.example.com">
<link rel="dns-prefetch" href="https://ads.example.com">

7. Забытый crossorigin на шрифтах

Шрифты по спецификации всегда загружаются через CORS — даже с вашего же домена. Без crossorigin в preload браузер загрузит шрифт дважды: раз по preload (без CORS) и второй — по правилу @font-face (с CORS). Двойная загрузка — двойная потеря времени.

<!-- ПЛОХО: загрузит шрифт дважды -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2">

<!-- ХОРОШО -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

Измерение результатов

Chrome DevTools: вкладка Network

После внесения изменений первым делом проверьте результат в DevTools. Включите столбец Priority на вкладке Network и убедитесь:

  • LCP-ресурс имеет приоритет High или Highest
  • LCP-ресурс начинает загружаться в числе первых
  • Некритичные ресурсы на Low или Lowest
  • Нет дублирующихся запросов (верный признак неправильного preload)

Для детального анализа переключитесь на вкладку Performance: запишите профиль загрузки и на Waterfall проверьте порядок ресурсов относительно маркера LCP.

Lighthouse

Lighthouse автоматически проверяет ряд аспектов приоритизации:

  • «Preload Largest Contentful Paint image» — предлагает добавить preload для LCP-изображения
  • «Preconnect to required origins» — показывает домены для preconnect
  • «Eliminate render-blocking resources» — выявляет CSS и JS, блокирующие рендеринг
  • «Prioritize Largest Contentful Paint image» — предлагает fetchpriority="high"

Запустите Lighthouse в режиме Mode: Navigation, Device: Mobile — мобильные условия жёстче и лучше выявляют проблемы. Сравните показатели до и после.

WebPageTest

Для тех, кому нужна максимальная детализация, есть WebPageTest:

  • Waterfall View — визуальный анализ порядка загрузки. Вы буквально увидите, сместился ли LCP-ресурс вверх
  • Connection View — проверка эффекта preconnect
  • Filmstrip — покадровое сравнение до и после
  • Request Details — приоритеты, включая начальный и финальный

Для тестирования 103 Early Hints используйте First View (не Repeat View) — Early Hints критичны именно при первом посещении.

DebugBear и инструменты мониторинга

Для отслеживания динамики во времени пригодятся инструменты постоянного мониторинга:

  • DebugBear — отслеживает приоритеты ресурсов, Waterfall с приоритетами, алерты при деградации
  • SpeedCurve — визуальное сравнение загрузки, трекинг Core Web Vitals
  • Google Search Console — реальные полевые данные CWV от пользователей
  • Chrome User Experience Report (CrUX) — полевые данные по LCP, FCP, TTFB

Не ограничивайтесь лабораторными тестами. Обязательно проверяйте полевые данные (RUM) — оптимизация приоритетов может давать разный эффект в зависимости от сети и устройства пользователя.

Программное измерение в коде

Для продакшн-мониторинга используйте Resource Timing API и PerformanceObserver:

// Мониторинг времени загрузки LCP-ресурса
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.element) {
      const resourceUrl = entry.url;
      const lcpTime = entry.startTime;

      // Найти Resource Timing для этого ресурса
      const resources = performance.getEntriesByType('resource');
      const lcpResource = resources.find(r => r.name === resourceUrl);

      if (lcpResource) {
        console.log('LCP Resource Timing:', {
          url: lcpResource.name,
          startTime: lcpResource.startTime.toFixed(0) + ' ms',
          responseEnd: lcpResource.responseEnd.toFixed(0) + ' ms',
          duration: lcpResource.duration.toFixed(0) + ' ms',
          transferSize: lcpResource.transferSize + ' bytes',
          dns: (lcpResource.domainLookupEnd - lcpResource.domainLookupStart).toFixed(0) + ' ms',
          connect: (lcpResource.connectEnd - lcpResource.connectStart).toFixed(0) + ' ms',
          ttfb: (lcpResource.responseStart - lcpResource.requestStart).toFixed(0) + ' ms',
          download: (lcpResource.responseEnd - lcpResource.responseStart).toFixed(0) + ' ms',
        });
      }

      // Отправить данные в систему мониторинга
      sendToAnalytics({
        metric: 'lcp',
        value: lcpTime,
        resourceDuration: lcpResource?.duration,
      });
    }
  }
}).observe({ type: 'largest-contentful-paint', buffered: true });

function sendToAnalytics(data) {
  // Отправка через navigator.sendBeacon для надёжности
  navigator.sendBeacon('/api/metrics', JSON.stringify(data));
}

Полный пример оптимизированного <head>

Вот как выглядит полностью оптимизированный <head> для e-commerce страницы продукта. Порядок элементов здесь критически важен — не меняйте его:

<head>
  <!-- 1. Meta charset — всегда первым -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Товар — Магазин</title>

  <!-- 2. Preconnect — как можно раньше -->
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link rel="preconnect" href="https://cdn.example.com">
  <link rel="dns-prefetch" href="https://analytics.example.com">

  <!-- 3. Preload критических ресурсов -->
  <link
    rel="preload"
    href="/fonts/inter-var.woff2"
    as="font"
    type="font/woff2"
    crossorigin
  >
  <link
    rel="preload"
    href="https://cdn.example.com/images/product-hero.webp"
    as="image"
    fetchpriority="high"
  >

  <!-- 4. Критический CSS -->
  <link rel="stylesheet" href="/css/critical.css">

  <!-- 5. Некритический CSS с отложенной загрузкой -->
  <link
    rel="stylesheet"
    href="/css/non-critical.css"
    media="print"
    onload="this.media='all'"
  >
  <noscript>
    <link rel="stylesheet" href="/css/non-critical.css">
  </noscript>

  <!-- 6. Критический JS -->
  <script src="/js/app.js" defer></script>

  <!-- 7. Некритичный JS -->
  <script src="/js/analytics.js" async fetchpriority="low"></script>

  <!-- 8. Prefetch для вероятной следующей страницы -->
  <link rel="prefetch" href="/catalog/related-products" as="document">
</head>

Чек-лист и заключение

Приоритизация загрузки ресурсов — мощный рычаг оптимизации, который удивительно часто остаётся неиспользованным. В 2026 году все основные браузеры поддерживают fetchpriority, preload, preconnect и 103 Early Hints, так что инструментарий у нас полный.

Итоговый чек-лист:

  • Определите LCP-элемент — через Lighthouse или DevTools Performance
  • Добавьте fetchpriority="high" на LCP-изображение (и убедитесь, что на нём нет loading="lazy")
  • Ограничьте preload 2–3 ресурсами — LCP-изображение, основной шрифт, критический CSS
  • Всегда указывайте as в preload — и crossorigin для шрифтов
  • Preconnect для 2–4 критических доменовdns-prefetch для остальных
  • Понизьте приоритет некритичногоfetchpriority="low" на аналитике, рекламе, второстепенных изображениях
  • loading="lazy" на изображениях ниже fold, но никогда на LCP
  • 103 Early Hints при TTFB выше 300 мс — на Cloudflare включается одним тумблером
  • Проверьте результат — столбец Priority в DevTools, Lighthouse, WebPageTest
  • Мониторьте полевые данные — CrUX, Search Console, RUM

Запомните главный принцип: приоритизация — это относительная система. Не нужно делать всё высокоприоритетным. Определите 1–2 самых важных ресурса, повысьте их, а остальное — понизьте. Именно такой контрастный подход даёт максимальный эффект.

И последнее. Управление приоритетами — не разовая акция, а постоянная практика. Каждое обновление сайта, каждый новый скрипт или виджет может сдвинуть баланс. Включите проверку приоритетов в свой регулярный аудит производительности — и LCP будет стабильно радовать.

Об авторе Editorial Team

Our team of expert writers and editors.