Implementing Resource Hints (preload, prefetch, preconnect, dns-prefetch)
Resource Hints are directives to browser: start this work now, don't wait for parser. Difference between properly placed hints and absence—300–800 ms on real mobile device. Incorrect hints can harm: occupy bandwidth needed for critical requests or evict critical resources from cache.
Four Directives and Their Purpose
dns-prefetch — resolves DNS for domain in advance. Costs few resources, saves 20–120 ms on first request to domain.
<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 — resolves DNS + establishes TCP connection + TLS handshake. Saves 100–500 ms. More expensive than dns-prefetch, use only for resources definitely needed on page.
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
preload — loads resource with high priority before browser discovers it in DOM. Critical for LCP image, main font, critical CSS.
<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>
<link rel="preload" href="/assets/main.js" as="script">
prefetch — loads resource with low priority for next page. Browser does this only when idle.
<link rel="prefetch" href="/checkout">
<link rel="prefetch" href="/assets/checkout-chunk.js" as="script">
When and What to Use
| Directive | When | Domain | Resource |
|---|---|---|---|
dns-prefetch |
Third-party domains, not 100% needed | Any third-party | — |
preconnect |
Third-party domains, definitely needed | Any third-party | — |
preload |
Critical resources of current page | Any | LCP-img, fonts, critical CSS |
prefetch |
Resources of next page | Any | JS chunks, HTML, data |
Implementation in 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',
];
if ($request->routeIs('catalog.*')) {
$hints[] = '</assets/product-page-chunk.js>; rel=prefetch; as=script';
}
$response->headers->set('Link', implode(', ', $hints));
return $response;
}
}
HTTP header Link works like <link> tag but browser gets it before HTML parsing starts.
Implementation in React/Next.js
In Next.js use <Head> component:
import Head from 'next/head';
export default function ProductPage({ product }) {
return (
<>
<Head>
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="dns-prefetch" href="//analytics.example.com" />
{product.heroImage && (
<link
rel="preload"
href={product.heroImage}
as="image"
fetchpriority="high"
/>
)}
</Head>
{/* ... */}
</>
);
}
Preload for Google Fonts Without FOUT
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<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>
Or self-host fonts:
<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>
Smart Prefetch on User Behavior
Prefetch on hover with delay:
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 });
});
Library quicklink does this systematically:
import quicklink from 'quicklink';
quicklink.listen({
ignores: [
/\/logout/,
/\/admin/,
(url) => url.includes('?'),
],
});
Common Mistakes
- Preload not used — resource preloaded but not used on page. Preload resource must be used on same page.
- Too many preconnect — more than 6 preconnect directives start interfering with main requests.
-
Preload fonts without
crossorigin— font loads twice. -
Both
dns-prefetchandpreconnect— redundant,preconnectincludes DNS resolution.
Timeframe
Audit current hints and implement basic set — 4–8 hours. Dynamic hints via HTTP headers + smart prefetch — 1–3 business days.







