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: <1msDNS 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
| Technique | DNS | TCP | TLS | Download | Render | Use Case |
|---|---|---|---|---|---|---|
| dns-prefetch | ✓ | ✗ | ✗ | ✗ | ✗ | Third-party domains |
| preconnect | ✓ | ✓ | ✓ | ✗ | ✗ | Critical third-parties |
| prefetch | ✓ | ✓ | ✓ | ✓ | ✗ | Next page resources |
| preload | ✓ | ✓ | ✓ | ✓ | ✗ | Current page critical resources |
| prerender | ✓ | ✓ | ✓ | ✓ | ✓ | Next 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 resourcesprefetch
✅ 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 resourcespreload
✅ Use for:
- Critical CSS
- Hero images
- Web fonts
- Primary JavaScript
❌ Don't use for:
- Everything (defeats prioritization)
- Below-fold resources
- Non-critical resourcesBest 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-100msEnable DNSSEC
DNSSEC adds cryptographic signatures to DNS records
Prevents DNS spoofing
Small performance cost (~5-10ms) for security benefitTTL Configuration
Short TTL (300s): Flexible, more DNS queries
Long TTL (86400s): Fewer queries, less flexible
Recommendation: 3600s (1 hour) for most resourcesTesting
Check DNS Timing
# Dig command
dig example.com
# Check query time
; Query time: 23 msecBrowser DevTools
Network tab → Timing:
- Queuing
- Stalled
- DNS Lookup ← Check this
- Initial connection
- TLS handshake
- Request sent
- Waiting (TTFB)
- Content downloadBest Practices Summary
- dns-prefetch: 5-10 third-party domains
- preconnect: 3-4 critical domains only
- Combine: Use both for critical resources
- Measure: Check DNS timing in DevTools
- Fast DNS: Use modern DNS providers
- Limit: Don't overdo preconnects
- 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!