Skip to content

Preload & Prefetch — Hinting the Browser About Critical and Future Resources

DodaTech Updated 2026-06-28 6 min read

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

  1. Preloading too many resources. Each preload is a network request. Preloading 20+ resources competes with critical rendering. Limit to 3-5 preloads per page.
  2. 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.
  3. Prefetching the current page or already-loaded resources. Prefetching an already cached resource wastes the opportunity. Track what's already loaded.
  4. Not using crossorigin for fonts. Fonts from CDNs need the crossorigin attribute. Without it, the preload is ignored and the font downloads twice.
  5. 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

  1. What is the difference between preload and prefetch?
  2. When should you use preconnect vs dns-prefetch?
  3. Why must font preloads include the crossorigin attribute?
  4. How does prefetch affect performance vs bandwidth usage?
  5. 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

Does preload guarantee the resource loads before it's needed?

Preload gives the browser a head start but doesn't guarantee timing. The browser still prioritizes based on resource type and current activity.

Can prefetch be abused to waste bandwidth?

Yes. Prefetching 10MB of resources on mobile data is user-hostile. Use prefetch sparingly and consider the user's connection (navigator.connection.effectiveType).

What is the priority order of resource hints?

Preload (highest priority, immediate fetch) > Preconnect (early connection setup) > DNS-Prefetch (DNS only) > Prerender (full page prefetch).

Does prefetch work with dynamic imports?

Yes. Prefetch the chunk URL to load it in idle time. When the dynamic import executes, the chunk may already be in cache, making navigation instant.

How do I preload images from a database?

Inject preload tags server-side after querying the hero image URL. Use the data-hero attribute on images and generate preload tags during page generation.

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