HTML Performance Optimization
Build lightning-fast websites with HTML optimization techniques. Learn about resource hints, lazy loading, code splitting, and Core Web Vitals for optimal user experience.
Why Performance Matters
Fast websites provide better user experience, higher engagement, better SEO rankings, and increased conversions. Even a 1-second delay can significantly impact user satisfaction.
53%
of mobile users abandon sites that take over 3 seconds to load
100ms
faster = 1% increase in conversions (Amazon study)
2x
better ranking potential with fast Core Web Vitals
Core Web Vitals
Google's metrics for measuring user experience:
LCP - Largest Contentful Paint
Measures: Loading performance
Goal: < 2.5 seconds
What it is: Time until largest content element is visible
FID - First Input Delay
Measures: Interactivity
Goal: < 100 milliseconds
What it is: Time from first interaction to browser response
CLS - Cumulative Layout Shift
Measures: Visual stability
Goal: < 0.1
What it is: How much content moves during page load
Resource Hints
Tell the browser what resources to prioritize:
1. DNS Prefetch
Resolve DNS early for external domains:
<head>
<!-- Resolve DNS for external resources -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://analytics.google.com">
</head>2. Preconnect
Establish early connection (DNS + TCP + TLS):
<head>
<!-- Full connection for critical resources -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</head>3. Prefetch
Download resources for future navigation:
<head>
<!-- Fetch resources user might need next -->
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/images/hero-next.jpg">
<link rel="prefetch" as="script" href="/analytics.js">
</head>4. Preload
High-priority resources needed for current page:
<head>
<!-- Critical resources for current page -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/images/hero.jpg" as="image">
<link rel="preload" href="/js/main.js" as="script">
</head>| Hint | When | What It Does | Use Case |
|---|---|---|---|
dns-prefetch | Future navigation | Resolves DNS only | External domains |
preconnect | Current/soon | DNS + TCP + TLS | Critical external resources |
prefetch | Future navigation | Downloads resource | Next page resources |
preload | Current page | Downloads with priority | Critical current resources |
Lazy Loading
Native Image Lazy Loading
<!-- Lazy load images below the fold -->
<img src="image1.jpg" alt="Above fold" loading="eager">
<img src="image2.jpg" alt="Below fold" loading="lazy">
<img src="image3.jpg" alt="Below fold" loading="lazy">
<!-- Also works for iframes -->
<iframe src="https://youtube.com/embed/..." loading="lazy"></iframe>Lazy Loading with Intersection Observer
<img data-src="image.jpg" class="lazy" alt="Lazy loaded image">
<script>
const lazyImages = document.querySelectorAll('img.lazy');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
}, {
rootMargin: '50px' // Start loading 50px before visible
});
lazyImages.forEach(img => imageObserver.observe(img));
</script>Minimizing Layout Shift (CLS)
Reserve Space for Images
<img src="photo.jpg" alt="Photo">
<!-- Image loads, page jumps! --><img src="photo.jpg" alt="Photo" width="800" height="600">
<!-- Browser reserves space, no jump -->Aspect Ratio for Responsive Images
<style>
img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9; /* Reserve space even before load */
}
</style>
<img src="photo.jpg" alt="Photo" width="1600" height="900">Reserve Space for Ads/Embeds
<div class="ad-container" style="min-height: 250px;">
<!-- Ad loads here -->
</div>
<div class="video-container" style="aspect-ratio: 16/9;">
<iframe src="..." loading="lazy"></iframe>
</div>Critical CSS
Inline critical CSS for above-the-fold content:
<head>
<!-- Inline critical CSS (above-the-fold styles) -->
<style>
/* Critical styles for initial render */
body { margin: 0; font-family: Arial; }
header { background: #333; color: white; padding: 1rem; }
.hero { min-height: 400px; }
</style>
<!-- Defer non-critical CSS -->
<link rel="preload" href="/css/main.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
</head>Script Loading Strategies
Default (Blocking)
<script src="script.js"></script>
<!-- Blocks HTML parsing until script loads and executes -->Async
<script src="analytics.js" async></script>
<!-- Downloads in parallel, executes when ready (may block parsing) -->
<!-- Good for: independent scripts (analytics, ads) -->Defer
<script src="main.js" defer></script>
<!-- Downloads in parallel, executes after HTML parsing -->
<!-- Good for: scripts that need DOM ready -->| Attribute | Download | Execute | Use For |
|---|---|---|---|
| None (default) | Blocks parsing | Immediately | Critical scripts |
async | Parallel | When ready | Analytics, ads |
defer | Parallel | After parsing | DOM-dependent scripts |
Module Scripts
<!-- Deferred by default -->
<script type="module" src="app.js"></script>
<!-- Async module -->
<script type="module" src="widget.js" async></script>Optimizing Images
1. Use Correct Format
- WebP/AVIF: Best compression, modern browsers
- JPEG: Photos, universal support
- PNG: Graphics with transparency
- SVG: Logos, icons (scalable)
2. Responsive Images
<picture>
<source type="image/webp" srcset="image.webp">
<source type="image/jpeg" srcset="image.jpg">
<img src="image.jpg" alt="..." width="800" height="600" loading="lazy">
</picture>
<!-- Or with srcset -->
<img src="image-800.jpg"
srcset="image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w"
sizes="(max-width: 600px) 400px, 800px"
alt="..."
width="800"
height="600"
loading="lazy">3. Compression
- Use tools like TinyPNG, ImageOptim, Squoosh
- Serve appropriately sized images (don't serve 4000px for 400px display)
- Use CDN with automatic optimization
Reduce HTTP Requests
1. Inline Small Resources
<!-- Inline small SVGs -->
<svg width="24" height="24">...</svg>
<!-- Data URLs for tiny images -->
<img src="data:image/svg+xml,..." alt="Icon">
<!-- Inline critical CSS -->
<style>/* Critical CSS */</style>2. CSS Sprites (for icons)
<style>
.icon {
background-image: url('sprites.png');
width: 24px;
height: 24px;
}
.icon-home { background-position: 0 0; }
.icon-user { background-position: -24px 0; }
.icon-search { background-position: -48px 0; }
</style>3. Combine Files
- Bundle JavaScript files
- Concatenate CSS files
- Use build tools (Webpack, Vite, Parcel)
Minimize Render-Blocking Resources
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Title</title>
<!-- Preconnect to external domains -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Inline critical CSS -->
<style>/* Above-the-fold styles */</style>
<!-- Preload critical resources -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<!-- Async load non-critical CSS -->
<link rel="preload" href="/css/main.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
<!-- Defer JavaScript -->
<script src="/js/main.js" defer></script>
</head>Font Loading Optimization
<head>
<!-- Preconnect to font CDN -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Preload fonts -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<!-- Load fonts with display swap -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
rel="stylesheet">
</head>
<style>
/* Use font-display to prevent FOIT (Flash of Invisible Text) */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* Show fallback font while loading */
}
</style>Performance Monitoring
Lighthouse (Chrome DevTools)
- Open Chrome DevTools (F12)
- Go to Lighthouse tab
- Run audit for Performance
- Follow recommendations
Web Vitals Measurement
<!-- Use web-vitals library -->
<script type="module">
import {onCLS, onFID, onLCP} from 'https://unpkg.com/web-vitals@3?module';
onCLS(console.log);
onFID(console.log);
onLCP(console.log);
</script>Performance API
<script>
// Measure page load time
window.addEventListener('load', () => {
const perfData = performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
const connectTime = perfData.responseEnd - perfData.requestStart;
const renderTime = perfData.domComplete - perfData.domLoading;
console.log('Page Load Time:', pageLoadTime + 'ms');
console.log('Connect Time:', connectTime + 'ms');
console.log('Render Time:', renderTime + 'ms');
});
// Resource timing
performance.getEntriesByType('resource').forEach(resource => {
console.log(resource.name, resource.duration);
});
</script>Complete Optimized Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Page description for SEO">
<title>Page Title</title>
<!-- Resource hints -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://analytics.google.com">
<!-- Critical CSS inline -->
<style>
/* Above-the-fold critical styles */
body { margin: 0; font-family: system-ui; }
.hero { min-height: 400px; }
</style>
<!-- Preload critical resources -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/images/hero.webp" as="image">
<!-- Async CSS -->
<link rel="preload" href="/css/main.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
<!-- Deferred JavaScript -->
<script src="/js/main.js" defer></script>
</head>
<body>
<!-- Above-the-fold content -->
<header>
<h1>Page Title</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<!-- Hero with explicit dimensions -->
<section class="hero">
<picture>
<source type="image/webp" srcset="hero.webp">
<img src="hero.jpg" alt="Hero" width="1200" height="600" loading="eager">
</picture>
</section>
<!-- Below-the-fold content with lazy loading -->
<section>
<img src="image1.jpg" alt="..." width="800" height="600" loading="lazy">
<img src="image2.jpg" alt="..." width="800" height="600" loading="lazy">
</section>
</main>
<footer>
<p>© 2024 Company Name</p>
</footer>
</body>
</html>Performance Checklist
✅ HTML Optimization:
- ☑ Minify HTML in production
- ☑ Use semantic HTML
- ☑ Minimize DOM depth
- ☑ Remove unused code
✅ Images:
- ☑ Use modern formats (WebP, AVIF)
- ☑ Specify width and height
- ☑ Lazy load below-the-fold images
- ☑ Use responsive images (srcset)
- ☑ Compress images
✅ CSS:
- ☑ Inline critical CSS
- ☑ Async load non-critical CSS
- ☑ Minify CSS
- ☑ Remove unused CSS
✅ JavaScript:
- ☑ Use defer or async attributes
- ☑ Minify JavaScript
- ☑ Code splitting
- ☑ Tree shaking
✅ Fonts:
- ☑ Preload critical fonts
- ☑ Use font-display: swap
- ☑ Subset fonts
- ☑ Use WOFF2 format
Best Practices Summary
✅ Do
- Specify image dimensions
- Use resource hints appropriately
- Lazy load below-the-fold content
- Defer non-critical JavaScript
- Inline critical CSS
- Optimize images and use modern formats
- Monitor Core Web Vitals
- Test on real devices and connections
⌠Don't
- Block rendering with synchronous scripts
- Forget image dimensions (causes CLS)
- Load all images eagerly
- Use render-blocking CSS
- Serve unoptimized images
- Ignore performance metrics
- Test only on fast connections
- Assume performance is "good enough"