Реализация Resource Hints (preload, prefetch, preconnect, dns-prefetch) для сайта
Resource Hints — это директивы браузеру: начни делать эту работу прямо сейчас, не жди, пока парсер до неё доберётся. Разница между правильно расставленными подсказками и их отсутствием — 300–800 мс на реальном мобильном устройстве. Неправильно расставленные подсказки могут навредить: занять полосу пропускания нужными запросами или вытеснить критичные ресурсы из кеша.
Четыре директивы и их смысл
dns-prefetch — заранее резолвит DNS для домена. Стоит копейки по ресурсам, даёт 20–120 мс экономии при первом запросе к домену.
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//www.google-analytics.com">
preconnect — резолвит DNS + устанавливает TCP-соединение + TLS-хендшейк. Экономия 100–500 мс. Дороже чем dns-prefetch, поэтому только для ресурсов, которые точно понадобятся на странице.
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
Атрибут crossorigin нужен для ресурсов, которые запрашиваются с CORS (шрифты, скрипты с другого домена).
preload — загружает ресурс с высоким приоритетом до того, как браузер обнаружил его в DOM. Критично для LCP-изображения, основного шрифта, критичного CSS.
<!-- LCP-изображение -->
<link rel="preload" href="/hero-image.webp" as="image" fetchpriority="high">
<!-- Шрифт -->
<link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin>
<!-- Критичный JS-чанк -->
<link rel="preload" href="/assets/main.js" as="script">
Атрибут as обязателен — без него браузер загружает ресурс с низким приоритетом и без кеширования по типу.
prefetch — загружает ресурс с низким приоритетом для использования на следующей странице. Браузер делает это только в простое.
<!-- Следующая страница, на которую, вероятно, перейдёт пользователь -->
<link rel="prefetch" href="/checkout">
<link rel="prefetch" href="/assets/checkout-chunk.js" as="script">
Что и когда использовать
| Директива | Когда | Домен | Ресурс |
|---|---|---|---|
dns-prefetch |
Сторонние домены, не 100% нужные | Любой сторонний | — |
preconnect |
Сторонние домены, точно нужные | Любой сторонний | — |
preload |
Критичные ресурсы текущей страницы | Любой | LCP-img, шрифты, critical CSS |
prefetch |
Ресурсы следующей страницы | Любой | JS-чанки, HTML, данные |
Реализация в Laravel (серверная сторона)
// app/Http/Middleware/ResourceHints.php
class ResourceHints
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$hints = [
'<https://fonts.googleapis.com>; rel=preconnect',
'<https://fonts.gstatic.com>; rel=preconnect; crossorigin',
'</fonts/Inter-Regular.woff2>; rel=preload; as=font; type="font/woff2"; crossorigin',
];
// Для страниц каталога — prefetch страницы продукта
if ($request->routeIs('catalog.*')) {
$hints[] = '</assets/product-page-chunk.js>; rel=prefetch; as=script';
}
$response->headers->set(
'Link',
implode(', ', $hints)
);
return $response;
}
}
HTTP-заголовок Link работает так же, как тег <link> в HTML, но браузер получает его раньше — до начала парсинга HTML.
Реализация в React/Next.js
В Next.js используем компонент <Head>:
import Head from 'next/head';
export default function ProductPage({ product }) {
return (
<>
<Head>
{/* Preconnect к API и CDN */}
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="dns-prefetch" href="//analytics.example.com" />
{/* Preload LCP-изображения */}
{product.heroImage && (
<link
rel="preload"
href={product.heroImage}
as="image"
fetchpriority="high"
/>
)}
</Head>
{/* ... */}
</>
);
}
Для динамического preload на основе данных:
// Компонент определяет, что будет LCP-элементом
function HeroImage({ src, alt }) {
return (
<>
<Head>
<link rel="preload" href={src} as="image" fetchpriority="high" />
</Head>
<img src={src} alt={alt} loading="eager" fetchpriority="high" />
</>
);
}
Preload для Google Fonts без flash of unstyled text
<!-- 1. Preconnect -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- 2. Загружаем CSS шрифта асинхронно -->
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap">
</noscript>
Или хостим шрифты самостоятельно — лучше и для performance, и для GDPR:
<link rel="preload" href="/fonts/inter-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/inter-600.woff2" as="font" type="font/woff2" crossorigin>
Автоматический prefetch на основе поведения пользователя
Умный prefetch: предзагружаем ресурсы при hover на ссылку, а не заранее для всей страницы.
// Prefetch при наведении с задержкой 100ms
const prefetchCache = new Set();
document.addEventListener('mouseover', (e) => {
const link = e.target.closest('a[href]');
if (!link || prefetchCache.has(link.href)) return;
// Только внутренние ссылки
if (link.origin !== location.origin) return;
const timeout = setTimeout(() => {
const prefetch = document.createElement('link');
prefetch.rel = 'prefetch';
prefetch.href = link.href;
document.head.appendChild(prefetch);
prefetchCache.add(link.href);
}, 100);
link.addEventListener('mouseleave', () => clearTimeout(timeout), { once: true });
});
Библиотека quicklink делает это системно с учётом Network Information API:
import quicklink from 'quicklink';
// Prefetch всех видимых ссылок в зоне видимости
quicklink.listen({
ignores: [
/\/logout/,
/\/admin/,
(url) => url.includes('?'),
],
});
Частые ошибки
Preload без использования — браузер предупреждает в консоли (The resource ... was preloaded using link preload but not used within a few seconds). Preload-ресурс должен быть использован на той же странице.
Слишком много preconnect — больше 6 preconnect-директив начинают мешать основным запросам. Выбираем только реально критичные домены.
Preload шрифтов без crossorigin — шрифт загрузится дважды: один раз по preload, второй раз при реальном запросе.
dns-prefetch и preconnect для одного домена — это избыточно. preconnect включает в себя DNS resolution.
Сроки
Аудит текущих resource hints и внедрение базового набора (preconnect для сторонних доменов, preload для LCP-изображения и шрифтов) — 4–8 часов. Реализация динамических подсказок через HTTP-заголовки на серверной стороне + умный prefetch на основе навигации — 1–3 рабочих дня.







