Web Performance Optimization in 2024 - Complete Guide to Core Web Vitals

Muhammad Ubaid Raza
January 20, 2024

Web Performance Optimization in 2024: Complete Guide to Core Web Vitals

Web performance has never been more critical. With Google's Page Experience update and the increasing focus on Core Web Vitals, optimizing your website's performance directly impacts search rankings, user experience, and conversion rates. After optimizing hundreds of websites and achieving consistent 90+ Lighthouse scores, I'm sharing the most effective strategies that work in 2024.

Understanding Core Web Vitals in Depth

Core Web Vitals are the foundation of modern web performance. Let's break down each metric and how to optimize for them.

Largest Contentful Paint (LCP) - Loading Performance

LCP measures when the largest content element becomes visible. Target: under 2.5 seconds.

Common LCP Elements and Optimization Strategies

<!-- ❌ Unoptimized hero image -->
<img src="hero-image.jpg" alt="Hero" />

<!-- ✅ Optimized with multiple techniques -->
<img
  src="hero-image-800w.webp"
  srcset="
    hero-image-400w.webp   400w,
    hero-image-800w.webp   800w,
    hero-image-1200w.webp 1200w
  "
  sizes="(max-width: 768px) 100vw, 50vw"
  alt="Hero"
  loading="eager"
  fetchpriority="high"
  decoding="async"
/>

Advanced LCP Optimization Techniques

1. Resource Hints for Critical Assets

<!-- Preload critical resources -->
<link
  rel="preload"
  href="/fonts/inter-var.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>
<link rel="preload" href="/hero-image-800w.webp" as="image" />

<!-- DNS prefetch for external resources -->
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
<link rel="dns-prefetch" href="//analytics.google.com" />

2. Critical CSS Inlining

<style>
  /* Inline critical CSS for above-the-fold content */
  .hero {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    height: 100vh;
    display: flex;
    align-items: center;
  }
  .hero h1 {
    font-size: 3rem;
    color: white;
    font-weight: 700;
  }
</style>

<!-- Load non-critical CSS asynchronously -->
<link
  rel="preload"
  href="/styles/main.css"
  as="style"
  onload="this.onload=null;this.rel='stylesheet'"
/>

First Input Delay (FID) / Interaction to Next Paint (INP)

FID is being replaced by INP in 2024. INP measures responsiveness throughout the page lifecycle. Target: under 200ms.

JavaScript Optimization Strategies

1. Code Splitting and Lazy Loading

// ❌ Loading everything upfront
import { heavyLibrary } from "./heavy-library"
import { analytics } from "./analytics"
import { chatWidget } from "./chat-widget"

// ✅ Dynamic imports for non-critical code
const loadHeavyFeature = async () => {
  const { heavyLibrary } = await import("./heavy-library")
  return heavyLibrary
}

// ✅ Lazy load analytics
setTimeout(async () => {
  const { analytics } = await import("./analytics")
  analytics.init()
}, 3000)

// ✅ Load chat widget on user interaction
document.addEventListener(
  "scroll",
  async () => {
    if (window.scrollY > 1000) {
      const { chatWidget } = await import("./chat-widget")
      chatWidget.render()
    }
  },
  { once: true },
)

2. Web Workers for Heavy Computations

// main.js
const worker = new Worker("/js/data-processor.js")

worker.postMessage({ data: largeDataSet })
worker.onmessage = (e) => {
  const processedData = e.data
  updateUI(processedData)
}

// data-processor.js (Web Worker)
self.onmessage = function (e) {
  const { data } = e.data

  // Heavy computation that won't block the main thread
  const processed = data.map((item) => ({
    ...item,
    computed: expensiveCalculation(item),
  }))

  self.postMessage(processed)
}

3. Optimizing Event Handlers

// ❌ Blocking event handlers
document.addEventListener("scroll", () => {
  // Expensive DOM operations on every scroll
  updateNavigation()
  updateProgressBar()
  checkVisibility()
})

// ✅ Throttled and optimized handlers
import { throttle } from "lodash-es"

const optimizedScrollHandler = throttle(() => {
  // Use requestAnimationFrame for DOM updates
  requestAnimationFrame(() => {
    updateNavigation()
    updateProgressBar()
    checkVisibility()
  })
}, 16) // ~60fps

document.addEventListener("scroll", optimizedScrollHandler, { passive: true })

Cumulative Layout Shift (CLS) - Visual Stability

CLS measures unexpected layout shifts. Target: under 0.1.

Preventing Layout Shifts

1. Reserve Space for Dynamic Content

/* ❌ No space reserved for images */
.image-container img {
  width: 100%;
  height: auto;
}

/* ✅ Aspect ratio containers */
.image-container {
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

.image-container img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

2. Font Loading Optimization

/* ✅ Prevent font swap layout shifts */
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var.woff2") format("woff2");
  font-display: optional; /* Prevents layout shift */
  font-weight: 100 900;
}

/* Fallback font with similar metrics */
body {
  font-family:
    "Inter",
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    system-ui,
    sans-serif;
}

3. Skeleton Screens for Loading States

// ✅ Skeleton that matches final content dimensions
function ProductCardSkeleton() {
  return (
    <div className="product-card">
      <div
        className="skeleton-image"
        style={{ aspectRatio: "1/1", height: "200px" }}
      />
      <div
        className="skeleton-text"
        style={{ height: "24px", marginTop: "12px" }}
      />
      <div
        className="skeleton-text"
        style={{ height: "16px", width: "60%", marginTop: "8px" }}
      />
      <div
        className="skeleton-button"
        style={{ height: "40px", marginTop: "16px" }}
      />
    </div>
  )
}

Advanced Performance Optimization Techniques

1. Resource Loading Strategies

Priority Hints for Fine-tuned Loading

<!-- High priority for critical resources -->
<link rel="preload" href="/critical.css" as="style" fetchpriority="high" />
<img src="/hero.webp" fetchpriority="high" alt="Hero" />

<!-- Low priority for non-critical resources -->
<img
  src="/footer-logo.webp"
  fetchpriority="low"
  loading="lazy"
  alt="Footer logo"
/>
<script src="/analytics.js" fetchpriority="low" defer></script>

Service Worker for Advanced Caching

// sw.js - Advanced caching strategies
const CACHE_NAME = "app-v1"
const STATIC_CACHE = "static-v1"
const DYNAMIC_CACHE = "dynamic-v1"

// Cache strategies for different resource types
const cacheStrategies = {
  // Static assets - Cache first
  static: (request) => caches.match(request),

  // API calls - Network first with fallback
  api: async (request) => {
    try {
      const response = await fetch(request)
      const cache = await caches.open(DYNAMIC_CACHE)
      cache.put(request, response.clone())
      return response
    } catch (error) {
      return caches.match(request)
    }
  },

  // Images - Stale while revalidate
  images: async (request) => {
    const cache = await caches.open(STATIC_CACHE)
    const cached = await cache.match(request)

    const fetchPromise = fetch(request).then((response) => {
      cache.put(request, response.clone())
      return response
    })

    return cached || fetchPromise
  },
}

2. Modern Image Optimization

Next-gen Image Formats with Fallbacks

<picture>
  <!-- AVIF for supported browsers (best compression) -->
  <source srcset="/image.avif" type="image/avif" />

  <!-- WebP for most modern browsers -->
  <source srcset="/image.webp" type="image/webp" />

  <!-- JPEG fallback -->
  <img src="/image.jpg" alt="Description" loading="lazy" decoding="async" />
</picture>

Responsive Images with Art Direction

<picture>
  <!-- Mobile: cropped version -->
  <source
    media="(max-width: 768px)"
    srcset="/hero-mobile-400w.webp 400w, /hero-mobile-800w.webp 800w"
    sizes="100vw"
  />

  <!-- Desktop: full version -->
  <source
    media="(min-width: 769px)"
    srcset="/hero-desktop-800w.webp 800w, /hero-desktop-1600w.webp 1600w"
    sizes="50vw"
  />

  <img src="/hero-desktop-800w.webp" alt="Hero image" loading="eager" />
</picture>

3. CSS Performance Optimization

Critical CSS Extraction and Inlining

// Build-time critical CSS extraction
const critical = require("critical")

critical.generate({
  inline: true,
  base: "dist/",
  src: "index.html",
  dest: "index-critical.html",
  width: 1300,
  height: 900,
  minify: true,
  extract: true, // Extract critical CSS to separate file
  ignore: ["@font-face", /url\(/], // Ignore certain rules
})

CSS Containment for Performance

/* Isolate layout calculations */
.card {
  contain: layout style paint;
}

/* Optimize for animations */
.animated-element {
  contain: layout;
  will-change: transform;
}

/* Content containment for dynamic sections */
.dynamic-content {
  contain: size layout style paint;
}

4. JavaScript Bundle Optimization

Tree Shaking and Dead Code Elimination

// ❌ Importing entire library
import _ from "lodash"

// ✅ Import only what you need
import { debounce, throttle } from "lodash-es"

// ✅ Even better: use native alternatives
const debounce = (func, wait) => {
  let timeout
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout)
      func(...args)
    }
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
  }
}

Module Federation for Micro-frontends

// webpack.config.js
const ModuleFederationPlugin = require("@module-federation/webpack")

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "shell",
      remotes: {
        dashboard: "dashboard@http://localhost:3001/remoteEntry.js",
        profile: "profile@http://localhost:3002/remoteEntry.js",
      },
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true },
      },
    }),
  ],
}

Performance Monitoring and Measurement

1. Real User Monitoring (RUM)

// Custom performance monitoring
class PerformanceMonitor {
  constructor() {
    this.metrics = {}
    this.observer = null
    this.init()
  }

  init() {
    // Core Web Vitals monitoring
    this.observeCLS()
    this.observeLCP()
    this.observeFID()
    this.observeINP()
  }

  observeLCP() {
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries()
      const lastEntry = entries[entries.length - 1]
      this.metrics.lcp = lastEntry.startTime
      this.sendMetric("lcp", lastEntry.startTime)
    })

    observer.observe({ entryTypes: ["largest-contentful-paint"] })
  }

  observeCLS() {
    let clsValue = 0
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (!entry.hadRecentInput) {
          clsValue += entry.value
        }
      }
      this.metrics.cls = clsValue
    })

    observer.observe({ entryTypes: ["layout-shift"] })
  }

  observeINP() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        const duration = entry.processingEnd - entry.startTime
        this.metrics.inp = Math.max(this.metrics.inp || 0, duration)
        this.sendMetric("inp", duration)
      }
    })

    observer.observe({ entryTypes: ["event"] })
  }

  sendMetric(name, value) {
    // Send to analytics service
    if ("sendBeacon" in navigator) {
      navigator.sendBeacon(
        "/analytics",
        JSON.stringify({
          metric: name,
          value: value,
          timestamp: Date.now(),
          url: window.location.href,
        }),
      )
    }
  }
}

// Initialize monitoring
new PerformanceMonitor()

2. Performance Budgets

// performance-budget.js
const performanceBudget = {
  // Size budgets
  maxBundleSize: 250000, // 250KB
  maxImageSize: 100000, // 100KB
  maxCSSSize: 50000, // 50KB

  // Timing budgets
  maxLCP: 2500, // 2.5s
  maxFID: 100, // 100ms
  maxCLS: 0.1, // 0.1

  // Resource budgets
  maxRequests: 50,
  maxDOMNodes: 1500,
}

// Budget enforcement in CI/CD
function checkPerformanceBudget(metrics) {
  const violations = []

  Object.keys(performanceBudget).forEach((key) => {
    if (metrics[key] > performanceBudget[key]) {
      violations.push({
        metric: key,
        actual: metrics[key],
        budget: performanceBudget[key],
        overage: metrics[key] - performanceBudget[key],
      })
    }
  })

  if (violations.length > 0) {
    console.error("Performance budget violations:", violations)
    process.exit(1)
  }
}

Framework-Specific Optimizations

React Performance Patterns

// ✅ Optimized React component patterns
import { memo, useMemo, useCallback, lazy, Suspense } from "react"

// Memoization for expensive calculations
const ExpensiveComponent = memo(({ data, filter }) => {
  const filteredData = useMemo(
    () => data.filter((item) => item.category === filter),
    [data, filter],
  )

  const handleClick = useCallback((id) => {
    // Handle click logic
  }, [])

  return (
    <div>
      {filteredData.map((item) => (
        <ItemCard key={item.id} item={item} onClick={handleClick} />
      ))}
    </div>
  )
})

// Lazy loading for route-based code splitting
const Dashboard = lazy(() => import("./Dashboard"))
const Profile = lazy(() => import("./Profile"))

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  )
}

Vue.js Performance Optimizations

<template>
  <!-- Use v-show for frequently toggled elements -->
  <div v-show="isVisible" class="expensive-component">
    <!-- Use v-memo for expensive renders -->
    <div v-memo="[user.id, user.lastUpdated]">
      <UserProfile :user="user" />
    </div>
  </div>
</template>

<script setup>
import { ref, computed, defineAsyncComponent } from "vue"

// Async components for code splitting
const HeavyComponent = defineAsyncComponent(
  () => import("./HeavyComponent.vue"),
)

// Computed properties for expensive calculations
const expensiveValue = computed(() => {
  return heavyCalculation(props.data)
})
</script>

1. Edge Computing and CDN Optimization

// Cloudflare Workers example for edge optimization
addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const url = new URL(request.url)

  // Edge-side includes for personalization
  if (url.pathname.startsWith("/api/personalized")) {
    return await handlePersonalizedContent(request)
  }

  // Static asset optimization
  if (url.pathname.match(/\.(jpg|jpeg|png|webp|avif)$/)) {
    return await optimizeImage(request)
  }

  return fetch(request)
}

2. AI-Powered Performance Optimization

// Predictive prefetching based on user behavior
class PredictivePrefetcher {
  constructor() {
    this.userBehavior = []
    this.mlModel = null
    this.init()
  }

  async init() {
    // Load pre-trained model for predicting user navigation
    this.mlModel = await tf.loadLayersModel("/models/navigation-predictor.json")
    this.observeUserBehavior()
  }

  observeUserBehavior() {
    // Track user interactions
    document.addEventListener("click", (e) => {
      this.userBehavior.push({
        element: e.target.tagName,
        timestamp: Date.now(),
        position: { x: e.clientX, y: e.clientY },
      })
    })

    // Predict and prefetch likely next pages
    setInterval(() => {
      this.predictAndPrefetch()
    }, 5000)
  }

  async predictAndPrefetch() {
    if (this.userBehavior.length < 5) return

    const prediction = await this.mlModel.predict(
      this.preprocessBehaviorData(this.userBehavior),
    )

    const likelyPages = this.interpretPrediction(prediction)
    likelyPages.forEach((page) => this.prefetchPage(page))
  }
}

Conclusion: Building for Performance in 2024

Web performance optimization in 2024 requires a holistic approach that combines:

  1. Core Web Vitals mastery - Understanding and optimizing LCP, INP, and CLS
  2. Modern loading strategies - Priority hints, resource hints, and smart bundling
  3. Advanced caching - Service workers, edge computing, and intelligent prefetching
  4. Continuous monitoring - Real user monitoring and performance budgets
  5. Framework optimization - Leveraging framework-specific performance features

The key to success is measuring everything, optimizing iteratively, and staying current with evolving web standards. Performance isn't a one-time task – it's an ongoing commitment to delivering exceptional user experiences.

Remember: a 1-second improvement in load time can increase conversions by up to 7%. In 2024, performance isn't just about user experience – it's about business success.

What performance challenges are you facing in your projects? Have you implemented any of these techniques? I'd love to hear about your experiences and help solve any performance bottlenecks you're encountering.