Preload & Prefetch — Hinting the Browser About Critical and Future Resources
In this tutorial, you will learn about Preload & Prefetch. We cover key concepts, practical examples, and best practices to help you master this topic.
Preload and prefetch directives tell the browser to fetch resources early, improving perceived performance for critical assets and future navigation.
What You'll Learn
By the end of this tutorial, you'll understand the difference between preload, prefetch, and preconnect, when to use each directive, how to preload critical resources, and how to avoid common mistakes.
Why It Matters
The browser discovers resources as it parses HTML. For critical resources (hero images, fonts, above-fold CSS), this may be too late. Preload hints the browser to start fetching immediately. Prefetch loads likely-next resources during idle time.
Real-World Use
A news site preloads the hero image and main CSS font, prefetches the next article's HTML and JS chunk, and preconnects to the CDN and analytics endpoint. The hero image starts loading 200ms earlier, and article navigation feels instant.
Resource Hint Types
graph LR
A[Resource Hints] --> B[Preload
Critical, needed now]
A --> C[Prefetch
Likely needed soon]
A --> D[Preconnect
Early connection setup]
A --> E[DNS-Prefetch
DNS resolution only]
B --> F[Fonts, hero images,
critical CSS/JS]
C --> G[Next page HTML,
likely chunks]
D --> H[Third-party origins,
CDNs, APIs]
E --> I[External domains
with resources]
style B fill:#e74c3c,color:#fff
style C fill:#f39c12,color:#fff
style D fill:#4a90d9,color:#fff
style E fill:#27ae60,color:#fff
Preload Implementation
<!-- Preload critical resources -->
<!-- Fonts — preload to avoid FOIT -->
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
<!-- Hero image — start loading immediately -->
<link rel="preload" href="/images/hero-1200.webp" as="image" type="image/webp">
<!-- Critical CSS — above the fold styles -->
<link rel="preload" href="/css/critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<!-- Critical JavaScript — needed for initial render -->
<link rel="preload" href="/js/initial.js" as="script">
<!-- Preload with media queries — only load for certain screens -->
<link rel="preload" href="/images/hero-desktop.webp" as="image" media="(min-width: 768px)">
<link rel="preload" href="/images/hero-mobile.webp" as="image" media="(max-width: 767px)">
<!-- Preload a dynamic import chunk -->
<link rel="preload" href="/assets/dashboard.chunk.js" as="script">
Prefetch Implementation
<!-- Prefetch likely next page resources -->
<!-- Prefetch the next page's HTML -->
<link rel="prefetch" href="/products" as="document">
<!-- Prefetch JavaScript chunks for likely navigation -->
<link rel="prefetch" href="/assets/products.chunk.js" as="script">
<link rel="prefetch" href="/assets/product-detail.chunk.js" as="script">
<!-- Prefetch images from next page -->
<link rel="prefetch" href="/images/products-banner.webp" as="image">
<!-- Prefetch with low priority -->
<link rel="prefetch" href="/assets/admin.chunk.js" as="script">
<!-- Prefetch for the next article in a blog sequence -->
<link rel="prefetch" href="/blog/next-article" as="document">
Dynamic Preloading
// lib/resource-loader.js — Dynamic resource loading
class ResourceLoader {
constructor() {
this.preloaded = new Set();
this.prefetched = new Set();
}
// Preload a resource immediately (high priority)
preload(url, options = {}) {
if (this.preloaded.has(url)) return;
this.preloaded.add(url);
const link = document.createElement('link');
link.rel = 'preload';
link.href = url;
link.as = options.as || 'fetch';
if (options.type) link.type = options.type;
if (options.crossorigin) link.crossOrigin = options.crossorigin;
if (options.media) link.media = options.media;
document.head.appendChild(link);
console.log(`Preloaded: ${url}`);
}
// Prefetch a resource (low priority, idle time)
prefetch(url, options = {}) {
if (this.prefetched.has(url)) return;
this.prefetched.add(url);
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
link.as = options.as || 'fetch';
document.head.appendChild(link);
console.log(`Prefetched: ${url}`);
}
// Preconnect to an origin
preconnect(url) {
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = url;
document.head.appendChild(link);
console.log(`Preconnected: ${url}`);
}
// DNS prefetch (even lighter than preconnect)
dnsPrefetch(url) {
const link = document.createElement('link');
link.rel = 'dns-prefetch';
link.href = url;
document.head.appendChild(link);
}
// Preload route chunks intelligently
preloadRoute(routePath, chunks) {
// Preload data API
this.preload(`/api${routePath}`, { as: 'fetch' });
// Preload JS chunks
chunks.forEach(chunk => {
this.preload(chunk, { as: 'script' });
});
}
// Prefetch based on user behavior
prefetchOnInteraction(element, url, type = 'prefetch') {
element.addEventListener('mouseenter', () => {
if (type === 'prefetch') {
this.prefetch(url);
} else {
this.preload(url);
}
}, { once: true });
element.addEventListener('touchstart', () => {
if (type === 'prefetch') {
this.prefetch(url);
} else {
this.preload(url);
}
}, { once: true });
}
}
const resourceLoader = new ResourceLoader();
// Example usage
resourceLoader.preload('/fonts/main.woff2', {
as: 'font',
type: 'font/woff2',
crossorigin: 'anonymous'
});
resourceLoader.preconnect('https://api.example.com');
resourceLoader.dnsPrefetch('https://images.example.com');
// Prefetch when hovering navigation
document.querySelectorAll('a[data-prefetch]').forEach(link => {
resourceLoader.prefetchOnInteraction(link, link.href, 'prefetch');
});
Preconnect and Third-Party Origins
<!-- Preconnect to third-party origins -->
<!-- Reduces connection negotiation time by 100-500ms -->
<!-- Analytics CDN -->
<link rel="preconnect" href="https://plausible.io">
<link rel="dns-prefetch" href="https://plausible.io">
<!-- Font CDN -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- API endpoint -->
<link rel="preconnect" href="https://api.example.com">
<!-- CDN for images -->
<link rel="preconnect" href="https://images.example.com">
<link rel="dns-prefetch" href="https://images.example.com">
<!-- Third-party embed services -->
<link rel="preconnect" href="https://www.youtube.com">
<link rel="dns-prefetch" href="https://www.youtube.com">
<link rel="preconnect" href="https://platform.twitter.com">
Performance Monitoring
// Measure the impact of resource hints
class HintEffectiveness {
constructor() {
this.hints = new Map();
this.resourceTimings = [];
}
trackHint(type, url) {
this.hints.set(url, {
type,
issuedAt: performance.now(),
loadedAt: null,
});
}
trackResource(url) {
if (this.hints.has(url)) {
this.hints.get(url).loadedAt = performance.now();
}
}
report() {
const results = [];
this.hints.forEach((hint, url) => {
const saving = hint.loadedAt
? hint.loadedAt - hint.issuedAt
: null;
results.push({
url,
type: hint.type,
timeToLoad: saving ? `${saving.toFixed(0)}ms` : 'Not measured',
saved: saving ? `Started ${saving.toFixed(0)}ms before request` : 'N/A'
});
});
console.table(results);
return results;
}
}
const hintMonitor = new HintEffectiveness();
// Usage
const link = document.createElement('link');
link.rel = 'preload';
link.href = '/critical-image.webp';
link.as = 'image';
hintMonitor.trackHint('preload', '/critical-image.webp');
document.head.appendChild(link);
Common Mistakes
- Preloading too many resources. Each preload is a network request. Preloading 20+ resources competes with critical rendering. Limit to 3-5 preloads per page.
- Preload without the as attribute. The as attribute tells the browser the resource type. Without it, the browser can't prioritize or apply appropriate Content Security Policy.
- Prefetching the current page or already-loaded resources. Prefetching an already cached resource wastes the opportunity. Track what's already loaded.
- Not using crossorigin for fonts. Fonts from CDNs need the crossorigin attribute. Without it, the preload is ignored and the font downloads twice.
- Preconnecting to too many origins. Each preconnect opens a TCP connection. 3-5 preconnects is reasonable. More than 10 wastes resources on idle connections.
Practice Questions
- What is the difference between preload and prefetch?
- When should you use preconnect vs dns-prefetch?
- Why must font preloads include the crossorigin attribute?
- How does prefetch affect performance vs bandwidth usage?
- How do you measure the effectiveness of resource hints?
Challenge: Implement resource hints for a multi-page site: preload the hero image and font, prefetch the next page's JS chunk, preconnect to the API and analytics domains, measure the time saved using Performance API, and present the results.
FAQ
Mini Project
Build a blog with intelligent resource hints: preload hero image and font (critical), prefetch the next article chunk (likely navigation), preconnect to analytics CDN and image CDN, dynamically prefetch chunks on link hover, and create a resource hint effectiveness dashboard.
What's Next
You've mastered preload and prefetch. Now learn about Preconnect & DNS-Prefetch for early connection setup to third-party origins.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro