Web Performance Optimization

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.

Optimizing First Contentful Paint (FCP)

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.

Prioritize Above-the-Fold 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 Resource Hints Strategically

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.

Optimize Critical Rendering Path

The critical rendering path determines how the browser turns HTML, CSS, and JS into pixels. Key steps:

  1. Parse HTML → build DOM
  2. Parse CSS → build CSSOM
  3. Combine DOM and CSSOM → render tree
  4. Layout → calculate positions
  5. Paint → paint pixels to screen

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.

Reducing Time-to-Interactive (TTI)

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.

Bundle and Tree-Shake JavaScript

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.

Defer Non-Critical JavaScript

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.

Minimize Main Thread Work

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.

Use Service Workers for Caching

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.

Optimizing Asset Delivery

Assets — images, fonts, videos — dominate payload. Optimize them for size and delivery.

Compress and Serve Images in Modern Formats

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.

Use Responsive Image Delivery

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.

Optimize Font Loading

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.

Monitoring Performance in Production

Performance is not a one-time task. It must be measured and maintained.

Capture Performance Metrics with the Performance API

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'] });

Collect Performance Data with Web Vitals

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.

Diagnose Performance Issues with DevTools

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.

Common Pitfalls and Silent Failures

Final Checklist

Performance 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.