Реализация Priority Hints для управления приоритетом загрузки ресурсов
Браузер сам расставляет приоритеты загрузки, и большую часть времени делает это правильно. Но есть сценарии, где его эвристики ошибаются: LCP-изображение загружается с низким приоритетом, потому что находится в карусели и браузер не знает, что оно главное. Или скрипт аналитики получает высокий приоритет и задерживает рендеринг. Priority Hints — fetchpriority атрибут — даёт возможность скорректировать эти решения.
Как браузер расставляет приоритеты без подсказок
Хром использует пять уровней приоритета: Highest, High, Medium, Low, Lowest. По умолчанию:
- CSS в
<head>— Highest - Синхронные скрипты в
<head>— High - Изображения — Low (кроме первых в viewport — Medium)
- Async/defer скрипты — Low
- Fetch API — High
- XHR — High
Проблема в том, что браузер не всегда знает, какое именно изображение является LCP-элементом. Если в <head> есть <link rel="preload"> для изображения — оно получает High. Но это только ускоряет его скачивание, не меняя приоритет рендеринга.
Синтаксис fetchpriority
Атрибут принимает три значения: high, low, auto (по умолчанию).
<!-- Повышаем приоритет LCP-изображения -->
<img src="/hero.webp" fetchpriority="high" alt="Hero">
<!-- Снижаем приоритет декоративных изображений ниже fold -->
<img src="/decoration.webp" fetchpriority="low" alt="">
<!-- Снижаем приоритет некритичного скрипта -->
<script src="/analytics.js" defer fetchpriority="low"></script>
<!-- В preload-директиве -->
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
В Fetch API:
// Критичный запрос данных для первого рендера
const data = await fetch('/api/initial-data', { priority: 'high' });
// Фоновая синхронизация
const sync = await fetch('/api/sync-status', { priority: 'low' });
Типовой сценарий: карусель изображений
Без подсказок браузер не знает, что первое изображение карусели — это LCP-элемент. Он загружает все изображения с одинаковым приоритетом.
<div class="carousel">
<!-- Первый слайд — LCP, высокий приоритет -->
<img
src="/slides/slide-1.webp"
fetchpriority="high"
loading="eager"
alt="Слайд 1"
>
<!-- Остальные слайды — низкий приоритет или lazy -->
<img
src="/slides/slide-2.webp"
fetchpriority="low"
loading="lazy"
alt="Слайд 2"
>
<img
src="/slides/slide-3.webp"
fetchpriority="low"
loading="lazy"
alt="Слайд 3"
>
</div>
Типовой сценарий: страница продукта с галереей
<!-- Главное изображение — это LCP -->
<img
src="/products/main-image.webp"
fetchpriority="high"
loading="eager"
width="800"
height="600"
alt="Название продукта"
>
<!-- Миниатюры — низкий приоритет -->
<div class="thumbnails">
<img src="/products/thumb-1.webp" fetchpriority="low" loading="lazy" alt="">
<img src="/products/thumb-2.webp" fetchpriority="low" loading="lazy" alt="">
<img src="/products/thumb-3.webp" fetchpriority="low" loading="lazy" alt="">
</div>
Динамическое управление приоритетом в React
Когда LCP-элемент определяется динамически (например, первый элемент в списке):
function ProductGrid({ products }) {
return (
<div className="grid">
{products.map((product, index) => (
<ProductCard
key={product.id}
product={product}
// Первый элемент в списке — потенциальный LCP
imagePriority={index === 0 ? 'high' : 'low'}
imageLoading={index < 4 ? 'eager' : 'lazy'}
/>
))}
</div>
);
}
function ProductCard({ product, imagePriority, imageLoading }) {
return (
<div>
<img
src={product.image}
fetchpriority={imagePriority}
loading={imageLoading}
alt={product.name}
width={400}
height={300}
/>
</div>
);
}
В Next.js компонент <Image> использует fetchpriority через проп priority:
import Image from 'next/image';
// Автоматически добавляет fetchpriority="high" и preload
<Image
src="/hero.webp"
width={1200}
height={600}
priority
alt="Hero"
/>
Снижение приоритета сторонних скриптов
Аналитика и пиксели не должны конкурировать с критичными ресурсами:
<!-- Вместо обычного defer: -->
<script src="https://www.google-analytics.com/analytics.js" defer></script>
<!-- С явно низким приоритетом: -->
<script src="https://www.google-analytics.com/analytics.js" defer fetchpriority="low"></script>
<!-- GTM с низким приоритетом -->
<script fetchpriority="low">
(function(w,d,s,l,i){/* GTM snippet */})(window,document,'script','dataLayer','GTM-XXXXX');
</script>
Критичные API-запросы при SSR hydration
В SPA при hydration часто нужно немедленно получить данные для первого рендера:
// Загрузчик данных с высоким приоритетом
async function loadCriticalData(url) {
const response = await fetch(url, {
priority: 'high',
// Данные нужны немедленно, не кешируем агрессивно
cache: 'no-cache',
});
return response.json();
}
// Фоновая синхронизация — низкий приоритет
async function syncUserPreferences() {
await fetch('/api/preferences', {
method: 'POST',
priority: 'low',
body: JSON.stringify(preferences),
});
}
Измерение эффекта
Проверяем через Chrome DevTools → Network → столбец Priority. До и после расстановки fetchpriority смотрим:
- С каким приоритетом загружается LCP-изображение
- Не вытесняют ли сторонние скрипты критичные ресурсы
Программно — через PerformanceResourceTiming:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.initiatorType === 'img' && entry.name.includes('hero')) {
console.log({
url: entry.name,
// Время получения первого байта
ttfb: entry.responseStart - entry.fetchStart,
// Полное время загрузки
duration: entry.duration,
});
}
}
});
observer.observe({ type: 'resource', buffered: true });
Поддержка браузерами
fetchpriority поддерживается в Chrome 101+, Edge 101+, Firefox 132+, Safari 17.2+. Для старых браузеров — просто игнорируется, деградация грациозная.
Проверить поддержку:
const img = document.createElement('img');
const supported = 'fetchPriority' in img;
Сроки
Аудит текущих приоритетов загрузки через DevTools и расстановка fetchpriority на ключевых элементах — 4–8 часов. Полноценное внедрение с динамическим управлением приоритетом в React-компонентах и серверной стороне — 1–2 рабочих дня.







