Введение: почему приоритизация ресурсов решает судьбу вашего 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) до того, как основной ответ будет готов. Звучит просто, но для динамических страниц это настоящая находка — серверу ведь нужно время на бизнес-логику, запросы к базе данных, рендеринг шаблона.
Обычный процесс выглядит так:
- Браузер отправляет запрос
- Сервер думает 200–800 мс (TTFB)
- Сервер отправляет ответ 200 с HTML
- Браузер парсит HTML и начинает загружать ресурсы
А с 103 Early Hints:
- Браузер отправляет запрос
- Сервер мгновенно отправляет ответ 103 с подсказками
- Браузер тут же начинает загружать подсказанные ресурсы
- Сервер думает свои 200–800 мс
- Сервер отправляет финальный ответ 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:
- Перейдите в Speed > Optimization > Content Optimization
- Включите Early Hints
- Убедитесь, что ваш сервер отправляет заголовки
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:
- Откройте DevTools (F12 или Ctrl+Shift+I)
- Перейдите на вкладку Network
- Правый клик на заголовке любого столбца
- Включите Priority
- Перезагрузите страницу с очисткой кэша (Ctrl+Shift+R)
- Изучите приоритеты
Обратите внимание на столбец 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 будет стабильно радовать.