Performance isn't just a technical metric — it directly impacts user retention, conversion rates, and SEO ranking. In this guide, you will learn how to measure, analyze, and dramatically improve web performance using modern APIs and caching techniques.


Step 1 — Core Web Vitals (CWV)

Google's Core Web Vitals are the official metrics used to rank your site's UX and speed.

The Three Pillars

  • LCP (Largest Contentful Paint): Loading speed. Measures when the largest image or text block becomes visible. Target: < 2.5 seconds.
  • INP (Interaction to Next Paint): Responsiveness. Measures the latency of every tap/click. Replaced FID. Target: < 200 milliseconds.
  • CLS (Cumulative Layout Shift): Visual stability. Measures how much elements jump around as the page loads. Target: < 0.1.
Bad (No Width/Height) Content Shifts Down! Good (Width/Height set) Reserved Space
Reserving space for an image prevents Cumulative Layout Shift

Step 2 — Resource Hints (Preload, Prefetch)

You can tell the browser what resources it will need soon, allowing it to fetch them early.

index.htmlhtml
<head>
  <!-- 1. Preconnect: Establish early network connection to external origin (DNS + TCP + TLS) -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  
  <!-- 2. Preload: Force high-priority fetch for critical resources used ON THIS PAGE -->
  <link rel="preload" href="hero-image.webp" as="image">
  <link rel="preload" href="critical-font.woff2" as="font" type="font/woff2" crossorigin>
  
  <!-- 3. Prefetch: Low-priority fetch for resources needed on the NEXT PAGE -->
  <link rel="prefetch" href="/about-page-bundle.js">
</head>

Step 3 — Image Optimization

Images are usually the largest payload on a page. Optimizing them is the easiest performance win.

images.htmlhtml
<!-- 1. Lazy Loading: Defer offscreen images until user scrolls near them -->
<img src="footer-logo.png" loading="lazy" alt="Logo" />

<!-- 2. Responsive Images: Let browser pick the right size for the screen width -->
<img 
  src="photo-800.jpg" 
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 50vw"
  alt="Event photo"
/>

<!-- 3. Modern Formats: Use AVIF or WebP with JPG fallback -->
<picture>
  <source srcset="photo.avif" type="image/avif">
  <source srcset="photo.webp" type="image/webp">
  <img src="photo.jpg" alt="Fallback">
</picture>

Step 4 — The Critical Rendering Path & JS Execution

When a browser encounters a standard <script> tag, it stops parsing HTML, downloads the script, and executes it. This is called Render Blocking.

scripts.htmlhtml
<!-- BAD: Blocks HTML parsing. Screen stays white until downloaded & executed. -->
<script src="app.js"></script>

<!-- ASYNC: Downloads in background, but pauses parsing to execute when ready. Good for analytics. -->
<script src="analytics.js" async></script>

<!-- DEFER (BEST): Downloads in background, executes ONLY after HTML parsing is complete. Maintains execution order. -->
<script src="app.js" defer></script>

Step 5 — Service Workers & Caching

Service Workers act as a programmable network proxy. They intercept HTTP requests and can serve cached responses instantly, enabling offline capabilities (PWAs).

sw.jsjavascript
const CACHE_NAME = 'app-cache-v1';

// Install event: Pre-cache core assets
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(['/index.html', '/styles.css', '/app.js']);
    })
  );
});

// Fetch event: Stale-While-Revalidate strategy
// Return cached version instantly, but fetch new version in background
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
      const fetchPromise = fetch(event.request).then(networkResponse => {
        caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, networkResponse.clone());
        });
        return networkResponse;
      });
      
      // Return cached immediately if exists, else wait for network
      return cachedResponse || fetchPromise;
    })
  );
});

Step 6 — Browser Observer APIs

Listening to scroll or resize events is notoriously bad for performance. Browser observers do this work natively off the main thread.

observer.jsjavascript
// Intersection Observer: Highly performant way to detect when element enters viewport
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // Element is visible! Trigger animation or lazy load.
      entry.target.classList.add('fade-in');
      // Stop observing once done
      observer.unobserve(entry.target);
    }
  });
}, { threshold: 0.1 }); // Trigger when 10% visible

document.querySelectorAll('.animate-on-scroll').forEach(el => observer.observe(el));