Verified by Garnet Grid

How to Optimize Web Performance: Core Web Vitals and Beyond

Achieve sub-2-second load times and pass Core Web Vitals. Covers image optimization, lazy loading, code splitting, CDN strategy, and Lighthouse auditing.

A 1-second delay in page load reduces conversions by 7%. A 3-second delay loses 53% of mobile visitors. Google uses Core Web Vitals as a ranking signal. Performance isn’t optional — it’s a business metric that directly affects revenue, SEO rankings, and user experience. This guide covers practical techniques to achieve sub-2-second load times and pass all Core Web Vitals, from quick wins to advanced optimizations.

The 80/20 rule of web performance: image optimization and code splitting solve 80% of performance problems. Start there before touching anything else.


The Three Core Web Vitals

MetricWhat It MeasuresGoodNeeds WorkPoor
LCP (Largest Contentful Paint)Loading speed — when does the main content appear?≤ 2.5s2.5-4.0s> 4.0s
INP (Interaction to Next Paint)Responsiveness — how fast does the page react to clicks?≤ 200ms200-500ms> 500ms
CLS (Cumulative Layout Shift)Visual stability — does the page jump around?≤ 0.10.1-0.25> 0.25

What Causes Each Problem

MetricCommon CausesQuick Wins
LCPLarge images, render-blocking CSS/JS, slow serverCompress images, preload LCP element, CDN
INPHeavy JavaScript, long tasks blocking main threadCode splitting, defer non-critical JS, web workers
CLSImages without dimensions, dynamic content injection, font swapsSet width/height, reserve ad slots, font-display: swap

Step 1: Audit Current Performance

# Lighthouse CLI audit
npx lighthouse https://yoursite.com \
  --output=json --output=html \
  --output-path=./lighthouse-report \
  --chrome-flags="--headless"

# PageSpeed Insights API (field + lab data)
curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://yoursite.com&strategy=mobile&key=$API_KEY" \
  | jq '.lighthouseResult.categories.performance.score'

# WebPageTest (detailed waterfall analysis)
# Visit webpagetest.org for filmstrip view + server timing

Lab Data vs Field Data

Data TypeSourceShowsUse For
Lab dataLighthouse, WebPageTestControlled test resultsDebugging, CI/CD gates
Field dataChrome UX Report, RUMReal user experienceSEO impact, actual performance

Step 2: Optimize Images (Biggest Impact)

Images typically account for 50-80% of page weight. Fixing images alone can cut load times in half.

2.1 Use Modern Formats

<!-- WebP with JPEG fallback, AVIF for maximum compression -->
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Hero image"
       width="1200" height="600"
       loading="lazy"
       decoding="async">
</picture>

Format Comparison

FormatCompressionBrowser SupportUse When
JPEGGoodUniversalFallback only
WebP25-35% smaller than JPEG97%+Default for most images
AVIF50% smaller than JPEG92%+Hero images, high-quality photos
SVGVector (smallest for icons)UniversalLogos, icons, illustrations

2.2 Responsive Images

<img
  srcset="
    hero-400.webp 400w,
    hero-800.webp 800w,
    hero-1200.webp 1200w,
    hero-1600.webp 1600w
  "
  sizes="(max-width: 600px) 400px,
         (max-width: 1024px) 800px,
         1200px"
  src="hero-800.webp"
  alt="Hero image"
  width="1200"
  height="600"
  loading="lazy"
>

2.3 Batch Optimization Script

# Convert all images to WebP with quality 80
for img in *.{jpg,png}; do
  cwebp -q 80 "$img" -o "${img%.*}.webp"
done

# Generate responsive sizes
for img in *.webp; do
  for size in 400 800 1200 1600; do
    convert "$img" -resize "${size}x" "${img%.*}-${size}.webp"
  done
done

# AVIF conversion for hero images
for img in hero*.{jpg,png}; do
  avifenc --min 20 --max 30 "$img" "${img%.*}.avif"
done

Step 3: Implement Code Splitting

// React — dynamic imports for route-based splitting
import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Reports = lazy(() => import('./pages/Reports'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/reports" element={<Reports />} />
      </Routes>
    </Suspense>
  );
}

Bundle Analysis

# Webpack Bundle Analyzer — find what's bloated
npx webpack-bundle-analyzer dist/stats.json

# Next.js built-in analyzer
ANALYZE=true npm run build

# Vite — rollup-plugin-visualizer
# Shows exactly which dependencies eat your bundle budget

Common Bundle Bloat Offenders

LibraryTypical Size (gzipped)Alternative
moment.js72 KBday.js (2 KB)
lodash (full)72 KBlodash-es (tree-shake) or native JS
chart.js65 KBchart.js/auto (tree-shake)
date-fns (full)40 KBImport specific functions

Step 4: Optimize CSS Delivery

<!-- Inline critical CSS (above-the-fold styles) -->
<style>
  /* Critical above-the-fold styles only */
  body { margin: 0; font-family: system-ui; }
  .hero { min-height: 100vh; display: grid; place-items: center; }
  .nav { position: sticky; top: 0; z-index: 100; }
</style>

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

Critical CSS Extraction

# Extract critical CSS automatically
npx critical https://yoursite.com \
  --base dist/ \
  --inline \
  --width 1300 --height 900

Step 5: CDN and Caching Strategy

# Nginx caching headers
location ~* \.(js|css|png|webp|avif|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

location ~* \.html$ {
    expires 10m;
    add_header Cache-Control "public, must-revalidate";
}

# Compression
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 256;

# Brotli (better compression than gzip)
brotli on;
brotli_types text/css application/javascript application/json;

CDN Caching Rules

Asset TypeCache DurationStrategyInvalidation
Static assets (JS, CSS, images)1 yearFingerprinted filenames (app.a3b8c.js)New build = new filename
HTML pages10 minutesStale-while-revalidateCDN purge on deploy
API responses0 (no-store)Origin onlyN/A
Fonts1 yearImmutable (self-hosted preferred)New filename

Step 6: Fix CLS (Layout Shift)

/* Always specify dimensions for images and videos */
img, video {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;  /* Reserves space before load */
}

/* Reserve space for ads */
.ad-slot {
  min-height: 250px;
  background: #f0f0f0;  /* Visual placeholder */
}

/* Prevent font swap layout shift */
@font-face {
  font-family: 'CustomFont';
  src: url('font.woff2') format('woff2');
  font-display: swap;
  size-adjust: 105%;  /* Match fallback font metrics to reduce shift */
}

Step 7: Preload Critical Resources

<head>
  <!-- Preload LCP image (highest priority) -->
  <link rel="preload" as="image" href="hero.webp" fetchpriority="high">

  <!-- Preconnect to third-party origins (DNS + TLS handshake) -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://cdn.yoursite.com">

  <!-- DNS prefetch for less critical third parties -->
  <link rel="dns-prefetch" href="https://www.google-analytics.com">
  <link rel="dns-prefetch" href="https://www.googletagmanager.com">
</head>

Resource Priority Hints

HintUse ForImpact
fetchpriority="high"LCP image, critical fontFaster above-the-fold rendering
fetchpriority="low"Below-fold images, non-critical scriptsPrevents contention with critical resources
loading="lazy"Below-fold imagesDefers download until near viewport
loading="eager"Above-fold LCP imageDownloads immediately (default)

Performance Budget

Set and enforce performance budgets in CI/CD:

MetricBudgetEnforcement
Total page weight< 1.5 MBCI/CD check (bundle analyzer)
JavaScript bundle< 300 KB (gzipped)Webpack/Vite config
CSS bundle< 50 KB (gzipped)Build check
LCP< 2.5 secondsLighthouse CI
INP< 200msReal User Monitoring
CLS< 0.1Lighthouse CI
# Lighthouse CI in GitHub Actions
npx @lhci/cli collect --url="https://staging.yoursite.com"
npx @lhci/cli assert \
  --preset=lighthouse:recommended \
  --assert.maxSize=1572864   # 1.5 MB

Performance Budget Template

Set concrete budgets and enforce them in CI/CD:

MetricTargetAcceptableUnacceptable
LCP (Largest Contentful Paint)Under 1.5sUnder 2.5sOver 2.5s
FID (First Input Delay)Under 50msUnder 100msOver 100ms
CLS (Cumulative Layout Shift)Under 0.05Under 0.1Over 0.1
Total JS bundle sizeUnder 200KBUnder 350KBOver 500KB
Total page weightUnder 1MBUnder 2MBOver 3MB
Time to InteractiveUnder 3sUnder 5sOver 5s
Number of HTTP requestsUnder 30Under 50Over 80

Quick Performance Audit Checklist

Run these checks on any page to find the biggest wins:

  1. Images — Are they using modern formats (WebP or AVIF)? Are they lazy-loaded below the fold?
  2. JavaScript — Is the bundle tree-shaken? Are unused libraries removed?
  3. CSS — Is critical CSS inlined? Is unused CSS purged?
  4. Fonts — Are you preloading? Using font-display swap? Subsetting?
  5. Third-party scripts — How many analytics or tracking scripts are loaded? Can any be deferred?

Performance Checklist

  • Lighthouse score > 90 on mobile (lab data)
  • Core Web Vitals passing in field data (Chrome UX Report)
  • Images in WebP/AVIF with responsive srcset
  • Lazy loading on below-the-fold images and iframes
  • Code splitting for route-based chunks (inspect bundle size)
  • Critical CSS inlined, non-critical CSS deferred
  • CDN with 1-year cache on fingerprinted static assets
  • Brotli/gzip compression enabled on server
  • All images and videos have explicit width/height (no CLS)
  • Fonts self-hosted with font-display: swap
  • LCP image preloaded with fetchpriority="high"
  • Performance budget enforced in CI/CD (blocks regressions)
  • Real User Monitoring (RUM) tracking deployed

:::note[Source] This guide is derived from operational intelligence at Garnet Grid Consulting. For performance audits, visit garnetgrid.com. :::

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →