Optimize web application performance using Core Web Vitals as a framework. Covers LCP, FID, CLS optimization, bundle analysis, lazy loading, image optimization, and the performance budget that keeps your app fast as it grows.
Web Performance Optimization is the practice of reducing time-to-interactive, minimizing network overhead, and ensuring smooth rendering across varied network conditions and device capabilities. It matters most when users expect instant feedback: on mobile, on slow 3G, on low-end devices, or when content is dense and interactive. It is the difference between a page that feels alive and one that feels sluggish or broken.
First Contentful Paint measures when the first piece of content renders. It is the first impression of a page’s responsiveness. To optimize it, reduce the time from navigation to visible content.
In the HTML, render critical content immediately. Extract the above-the-fold content into a critical.css bundle, and inline it in the <head>.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Home Page</title>
<style>
/* critical.css — inlined in <head> */
body { font-family: system-ui; margin: 0; padding: 1rem; }
header { background: #000; color: #fff; padding: 1rem; }
.hero { height: 300px; background: url(hero.jpg); background-size: cover; }
h1 { font-size: 2rem; }
.button { padding: 0.75rem 1.5rem; background: #007acc; color: white; border: none; border-radius: 4px; }
</style>
<link rel="preload" as="font" type="font/woff2" href="/fonts/inter.woff2" />
<link rel="preload" as="image" href="/images/hero.jpg" />
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header>
<h1>Welcome to Our Site</h1>
<p class="tagline">Fast, reliable, and beautiful.</p>
</header>
<section class="hero"></section>
<button class="button">Get Started</button>
<script src="/main.js" defer></script>
</body>
</html>
The preload hints ensure that fonts and the hero image are fetched early. The defer attribute on main.js ensures it loads without blocking render.
Use preconnect, prefetch, prerender, and dns-prefetch to guide the browser’s resource loading.
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />
<link rel="prefetch" href="/about" />
<link rel="prerender" href="/contact" />
preconnect establishes a full connection (DNS, TCP, TLS) to a host before resources are needed. prefetch downloads a resource in the background. prerender fully renders a page in a hidden tab, ready to show instantly.
The critical rendering path determines how the browser turns HTML, CSS, and JS into pixels. Key steps:
Ensure CSS is not render-blocking. Use async or defer on non-critical scripts.
<link rel="stylesheet" href="/critical.css" media="print" onload="this.media='all'">
The media="print" trick loads the CSS but delays rendering until it’s ready.
Time-to-Interactive measures when the page is responsive to user input. It depends on reducing JavaScript execution time and reducing the number of long tasks.
Use a bundler such as Webpack, Vite, or Rollup. Configure tree-shaking to eliminate unused code.
In webpack.config.js:
module.exports = {
mode: 'production',
optimization: {
usedExports: true,
sideEffects: false,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/
}
}
}
},
output: {
filename: 'js/[name].[contenthash].js',
chunkFilename: 'js/[name].[contenthash].chunk.js'
}
};
usedExports: true enables tree-shaking. sideEffects: false allows dead code elimination. splitChunks separates vendor code into its own bundle.
Load non-critical JS after the initial render using defer or async.
<script src="/analytics.js" async></script>
<script src="/comments.js" defer></script>
async downloads the script and executes it as soon as it’s ready. defer downloads in parallel with HTML parsing and executes after DOM is ready.
Long tasks (over 50ms) block the UI. Use PerformanceObserver to detect long tasks.
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn('Long task detected:', entry.name, entry.startTime, entry.duration);
}
}
});
observer.observe({ entryTypes: ['longtask'] });
Identify and optimize long tasks: avoid layout thrashing, use requestAnimationFrame, and batch DOM updates.
Implement a service worker to cache assets and serve from memory.
// service-worker.js
const CACHE_NAME = 'v1';
const urlsToCache = [
'/',
'/styles.css',
'/main.js',
'/images/logo.svg'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
return response || fetch(event.request);
})
);
});
Register the service worker in main.js:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then((registration) => {
console.log('SW registered:', registration.scope);
})
.catch((error) => {
console.error('SW registration failed:', error);
});
}
The service worker caches responses and serves them on subsequent visits, even offline.
Assets — images, fonts, videos — dominate payload. Optimize them for size and delivery.
Use WebP for photos and AVIF for high-quality images. Serve WebP by default, with JPEG or PNG as fallback.
<picture>
<source srcset="/image.webp" type="image/webp" />
<source srcset="/image.avif" type="image/avif" />
<img src="/image.jpg" alt="Description" loading="lazy" />
</picture>
Use loading="lazy" to defer image loading until the image is near the viewport.
Serve different image sizes based on viewport and device.
<picture>
<source media="(max-width: 600px)" srcset="/small.jpg 1x, /[email protected] 2x" />
<source media="(max-width: 1200px)" srcset="/medium.jpg 1x, /[email protected] 2x" />
<img src="/large.jpg" alt="Large image" loading="lazy" />
</picture>
Use srcset with multiple resolutions and sizes to control image selection.
Use font-display: swap and preload for fonts.
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
}
In HTML:
<link rel="preload" as="font" type="font/woff2" href="/fonts/inter.woff2" />
font-display: swap shows fallback text immediately while the font loads.
Performance is not a one-time task. It must be measured and maintained.
Use PerformanceObserver to capture navigation, resource, and paint entries.
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime);
}
if (entry.name === 'first-input') {
console.log('First Input Delay:', entry.processingStart - entry.startTime);
}
if (entry.name === 'largest-contentful-paint') {
console.log('LCP:', entry.startTime);
}
}
});
observer.observe({ entryTypes: ['paint', 'navigation', 'first-input'] });
Use the web-vitals library to collect Core Web Vitals.
import { getCLS, getFID, getLCP, getFCP, getTTFB } from 'web-vitals';
getCLS((metric) => {
console.log('Cumulative Layout Shift:', metric.value);
});
getFID((metric) => {
console.log('First Input Delay:', metric.value);
});
getLCP((metric) => {
console.log('Largest Contentful Paint:', metric.value);
});
getFCP((metric) => {
console.log('First Contentful Paint:', metric.value);
});
getTTFB((metric) => {
console.log('Time to First Byte:', metric.value);
});
These metrics can be sent to a backend or logged via window.performance.mark.
Use the Performance tab in browser DevTools to record and analyze the load of a page.
A common failure: layout thrashing — the browser recalculates layout on every DOM change. Fix by batching DOM updates with requestAnimationFrame.
preload hints: resources are not prioritized, leading to delayed FCP.async scripts: scripts load in parallel but execute in unpredictable order, causing race conditions.font-display values: block blocks content for too long; fallback causes flash of unstyled text.install event completes, but activate is missed, leading to stale caches.srcset not sized correctly: sizes attribute not aligned with media queries, causing unnecessary downloads.DOMContentLoaded fires before load event, but DOMContentLoaded can miss critical CSS.critical.css in <head>preload for fonts, images, and key scriptsasync or defer on non-critical JSweb-vitals metrics collected and sentpicture and srcset used for responsive imagesfont-display: swap with preload for fontsPerformanceObserver for long tasks and input delaycache-first and network-first strategies in service workercontenthash and chunkhash in output filenamespreconnect, prefetch, prerender for key resourcesPerformance is not a feature. It is the foundation of user experience. Optimize every byte, every millisecond, every interaction.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy