Front-end Engineering Lab

DNS Prefetch Strategies

Optimize DNS lookups with prefetch, preconnect, and other strategies

DNS Prefetch Strategies

DNS lookups add 20-200ms latency to every new domain. Optimizing DNS resolution can significantly improve load times, especially for third-party resources.

DNS Resolution Process

1. Browser cache (instant)
2. OS cache (~1ms)
3. Router cache (~5-10ms)
4. ISP DNS (~20-50ms)
5. Root servers + TLD + Authoritative (~100-200ms)

First visit: 100-200ms
Cached: <1ms

DNS Optimization Techniques

1. DNS Prefetch

Resolve DNS before resource is needed.

<!-- Prefetch DNS for third-party domains -->
<link rel="dns-prefetch" href="//cdn.example.com" />
<link rel="dns-prefetch" href="//api.example.com" />
<link rel="dns-prefetch" href="//analytics.google.com" />
<link rel="dns-prefetch" href="//fonts.googleapis.com" />

When it runs: As soon as browser sees it
What it does: DNS lookup only
Savings: 20-200ms per domain

2. Preconnect

DNS + TCP + TLS handshake.

<!-- Full connection to critical third-parties -->
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

When it runs: As soon as browser sees it
What it does: DNS + TCP + TLS (full connection)
Savings: 100-500ms per domain

3. Prefetch

Download resource for future navigation.

<!-- Prefetch resources for next page -->
<link rel="prefetch" href="/dashboard.js" />
<link rel="prefetch" href="/dashboard.css" />

When it runs: When browser is idle
What it does: Downloads full resource
Savings: Instant next page load

4. Preload

Download resource for current page.

<!-- High-priority resources -->
<link rel="preload" href="/critical.css" as="style" />
<link rel="preload" href="/hero.jpg" as="image" />
<link rel="preload" href="/app.js" as="script" />
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin />

When it runs: Immediately, high priority
What it does: Downloads resource now
Savings: Parallel loading

5. Prerender

Render entire page in background.

<!-- Prerender next page (use sparingly!) -->
<link rel="prerender" href="/checkout" />

When it runs: Immediately
What it does: Fully renders page in hidden tab
Savings: Instant navigation (but expensive)

Comparison Table

TechniqueDNSTCPTLSDownloadRenderUse Case
dns-prefetchThird-party domains
preconnectCritical third-parties
prefetchNext page resources
preloadCurrent page critical resources
prerenderNext page (expensive)

React/Next.js Implementation

DNS Prefetch

// app/layout.tsx
export default function RootLayout({ children }: Props) {
  return (
    <html>
      <head>
        {/* DNS prefetch for third-parties */}
        <link rel="dns-prefetch" href="//cdn.example.com" />
        <link rel="dns-prefetch" href="//api.example.com" />
        <link rel="dns-prefetch" href="//analytics.google.com" />
      </head>
      <body>{children}</body>
    </html>
  );
}

Preconnect

// app/layout.tsx
export default function RootLayout({ children }: Props) {
  return (
    <html>
      <head>
        {/* Preconnect to critical origins */}
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
        <link rel="preconnect" href="https://cdn.example.com" />
      </head>
      <body>{children}</body>
    </html>
  );
}

Dynamic DNS Prefetch

// components/DynamicPrefetch.tsx
'use client';

import { useEffect } from 'react';

export function DynamicPrefetch({ domains }: { domains: string[] }) {
  useEffect(() => {
    domains.forEach(domain => {
      const link = document.createElement('link');
      link.rel = 'dns-prefetch';
      link.href = domain;
      document.head.appendChild(link);
    });
  }, [domains]);

  return null;
}

// Usage
<DynamicPrefetch domains={[
  '//cdn.example.com',
  '//api.example.com',
]} />

Prefetch on Hover

// components/PrefetchLink.tsx
'use client';

import Link from 'next/link';
import { useEffect, useRef, useState } from 'react';

export function PrefetchLink({ href, children }: Props) {
  const [prefetched, setPrefetched] = useState(false);
  const timeoutRef = useRef<NodeJS.Timeout>();

  const handleMouseEnter = () => {
    if (prefetched) return;
    
    // Prefetch after 100ms hover
    timeoutRef.current = setTimeout(() => {
      const link = document.createElement('link');
      link.rel = 'prefetch';
      link.href = href;
      document.head.appendChild(link);
      setPrefetched(true);
    }, 100);
  };

  const handleMouseLeave = () => {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
  };

  return (
    <Link
      href={href}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      {children}
    </Link>
  );
}

// Usage
<PrefetchLink href="/dashboard">
  Go to Dashboard
</PrefetchLink>

Intersection Observer Prefetch

// components/ViewportPrefetch.tsx
'use client';

import { useEffect, useRef } from 'react';

export function ViewportPrefetch({ href }: { href: string }) {
  const prefetchedRef = useRef(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && !prefetchedRef.current) {
          const link = document.createElement('link');
          link.rel = 'prefetch';
          link.href = href;
          document.head.appendChild(link);
          prefetchedRef.current = true;
          observer.disconnect();
        }
      },
      { rootMargin: '200px' }  // Prefetch 200px before visible
    );

    const element = document.querySelector(`a[href="${href}"]`);
    if (element) {
      observer.observe(element);
    }

    return () => observer.disconnect();
  }, [href]);

  return null;
}

// Usage
<ViewportPrefetch href="/next-page" />
<a href="/next-page">Next Page</a>

Third-Party Resource Optimization

Google Fonts

// app/layout.tsx
export default function RootLayout({ children }: Props) {
  return (
    <html>
      <head>
        {/* Preconnect to Google Fonts */}
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
        
        {/* Load font */}
        <link
          href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
          rel="stylesheet"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Google Analytics

// app/layout.tsx
export default function RootLayout({ children }: Props) {
  return (
    <html>
      <head>
        {/* DNS prefetch for analytics */}
        <link rel="dns-prefetch" href="//www.google-analytics.com" />
        <link rel="dns-prefetch" href="//www.googletagmanager.com" />
      </head>
      <body>
        {children}
        
        {/* Load analytics */}
        <script
          async
          src="https://www.googletagmanager.com/gtag/js?id=GA_ID"
        />
      </body>
    </html>
  );
}

CDN Resources

export default function RootLayout({ children }: Props) {
  return (
    <html>
      <head>
        {/* Preconnect to CDN */}
        <link rel="preconnect" href="https://cdn.example.com" />
        <link rel="dns-prefetch" href="//images.example.com" />
        <link rel="dns-prefetch" href="//static.example.com" />
      </head>
      <body>{children}</body>
    </html>
  );
}

When to Use Each

dns-prefetch

✅ Use for:
- Third-party domains you'll use later
- Analytics domains
- Ad networks
- Social media widgets
- Non-critical resources

❌ Don't use for:
- Same origin (no benefit)
- Critical resources (use preconnect)

preconnect

✅ Use for:
- Critical third-party domains
- API origins
- Font providers
- Critical CDN resources

❌ Don't use for:
- More than 4-6 domains (too many connections)
- Same origin (already connected)
- Non-critical resources

prefetch

✅ Use for:
- Next page resources
- Route-based code splitting
- Resources for likely next action

❌ Don't use for:
- Current page resources (use preload)
- Unlikely navigation targets
- Large resources

preload

✅ Use for:
- Critical CSS
- Hero images
- Web fonts
- Primary JavaScript

❌ Don't use for:
- Everything (defeats prioritization)
- Below-fold resources
- Non-critical resources

Best Practices

1. Limit Preconnects

// ❌ BAD: Too many preconnects
<link rel="preconnect" href="https://domain1.com" />
<link rel="preconnect" href="https://domain2.com" />
<link rel="preconnect" href="https://domain3.com" />
<link rel="preconnect" href="https://domain4.com" />
<link rel="preconnect" href="https://domain5.com" />
<link rel="preconnect" href="https://domain6.com" />
<link rel="preconnect" href="https://domain7.com" />

// ✅ GOOD: 3-4 critical domains only
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="preconnect" href="https://api.example.com" />

2. Combine with Resource Hints

// Full optimization stack
<head>
  {/* 1. DNS prefetch for third-parties */}
  <link rel="dns-prefetch" href="//analytics.google.com" />
  
  {/* 2. Preconnect to critical origins */}
  <link rel="preconnect" href="https://fonts.googleapis.com" />
  <link rel="preconnect" href="https://cdn.example.com" />
  
  {/* 3. Preload critical resources */}
  <link rel="preload" href="/critical.css" as="style" />
  <link rel="preload" href="/hero.webp" as="image" />
  <link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossOrigin />
  
  {/* 4. Load stylesheets */}
  <link rel="stylesheet" href="/critical.css" />
</head>

3. Measure Impact

// Measure DNS + connection time
performance.getEntriesByType('resource').forEach(entry => {
  if (entry.name.includes('third-party.com')) {
    console.log('DNS:', entry.domainLookupEnd - entry.domainLookupStart);
    console.log('TCP:', entry.connectEnd - entry.connectStart);
    console.log('TLS:', entry.connectEnd - entry.secureConnectionStart);
  }
});

DNS Provider Optimization

Use Fast DNS Providers

Cloudflare DNS (1.1.1.1):    ~10-20ms
Google DNS (8.8.8.8):        ~20-30ms
Quad9 DNS (9.9.9.9):         ~20-30ms
ISP DNS:                     ~50-100ms

Enable DNSSEC

DNSSEC adds cryptographic signatures to DNS records
Prevents DNS spoofing
Small performance cost (~5-10ms) for security benefit

TTL Configuration

Short TTL (300s):  Flexible, more DNS queries
Long TTL (86400s): Fewer queries, less flexible

Recommendation: 3600s (1 hour) for most resources

Testing

Check DNS Timing

# Dig command
dig example.com

# Check query time
; Query time: 23 msec

Browser DevTools

Network tab → Timing:
- Queuing
- Stalled
- DNS Lookup  ← Check this
- Initial connection
- TLS handshake
- Request sent
- Waiting (TTFB)
- Content download

Best Practices Summary

  1. dns-prefetch: 5-10 third-party domains
  2. preconnect: 3-4 critical domains only
  3. Combine: Use both for critical resources
  4. Measure: Check DNS timing in DevTools
  5. Fast DNS: Use modern DNS providers
  6. Limit: Don't overdo preconnects
  7. Test: Verify on slow connections

Common Pitfalls

Too many preconnects: Wastes resources
3-4 critical domains only

Same origin: No benefit
Third-party domains only

No crossorigin: Fonts won't load
crossorigin for fonts

Preconnect everything: Defeats prioritization
Reserve for truly critical domains

DNS optimization is often overlooked but can save 100-500ms per third-party domain—add these hints and see immediate improvements!

On this page