StackPractices
intermediate By Mathias Paulenko

Web Performance Optimization

Improve Core Web Vitals, reduce bundle sizes, and optimize frontend performance with lazy loading, code splitting, and modern build tools.

Overview

Web performance directly impacts user engagement, conversion rates, and search rankings. Google’s Core Web Vitals — Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS) — provide measurable targets. This resource covers practical techniques: lazy loading, code splitting, image optimization, critical CSS, and modern build tooling to hit sub-3-second page loads.

When to Use

Use this resource when:

  • Core Web Vitals scores are failing (LCP > 2.5s, CLS > 0.1)
  • Mobile users on 3G networks abandon pages before they load
  • Bundle sizes exceed 200KB and impact time-to-interactive
  • Third-party scripts (analytics, ads) block the main thread

Solution

Critical CSS Inline + Async Load (HTML)

<head>
  <!-- Inline critical CSS (~14KB max) -->
  <style>
    /* Above-fold styles: header, hero, layout skeleton */
    body{margin:0;font-family:system-ui}
    .hero{background:#3b82f6;min-height:60vh}
  </style>

  <!-- Preload key resources -->
  <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="preload" href="/hero-image.webp" as="image" fetchpriority="high">

  <!-- Async load non-critical CSS -->
  <link rel="preload" href="/styles.css" as="style" onload="this.rel='stylesheet'">
</head>

Lazy Loading Images with Native API

<!-- Native lazy loading — no JavaScript required -->
<img src="hero.webp" alt="Hero" fetchpriority="high" width="800" height="400">
<img src="below-fold-1.webp" alt="Product" loading="lazy" width="400" height="300">
<img src="below-fold-2.webp" alt="Team" loading="lazy" width="400" height="300">

Code Splitting with Live Imports (React)

import { lazy, Suspense } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));
const AnalyticsDashboard = lazy(() => import('./AnalyticsDashboard'));

function Dashboard() {
  return (
    <div>
      <CriticalStats /> {/* Always loaded */}
      <Suspense fallback={<Spinner />}>
        <HeavyChart /> {/* Loaded on demand */}
      </Suspense>
      <Suspense fallback={<Spinner />}>
        <AnalyticsDashboard /> {/* Separate chunk */}
      </Suspense>
    </div>
  );
}

Explanation

Core Web Vitals targets:

MetricGoodPoorMeasures
LCP< 2.5s> 4sLargest visible element load time
INP< 200ms> 500msInteraction responsiveness
CLS< 0.1> 0.25Visual stability (layout shifts)
TTFB< 600ms> 1.8sTime to first byte

Performance budget example:

  • JavaScript: 150KB (gzipped)
  • Images: 250KB total
  • CSS: 50KB (including critical inline)
  • Fonts: 40KB (subsetted)
  • Third-party: 100KB max

Variants

TechniqueImpactEffort
Image optimization (WebP/AVIF)-50% image bytesLow
Font subsetting-80% font bytesLow
Code splitting-60% initial JSMedium
Edge caching-90% TTFBLow
Service WorkerInstant repeat visitsMedium
HTTP/3 + QUICFaster on lossy networksLow (CDN)

What Works

  • Measure real users, not lab tests: Field data from Chrome UX Report reflects actual conditions
  • Optimize the critical path: Anything blocking <head> should be under 50KB total. See server-side rendering.
  • Self-host fonts and analytics: Third-party connections add DNS + TLS + TCP overhead
  • Use content-visibility: auto: Browsers skip rendering off-screen content
  • Defer non-critical JavaScript: defer or type="module" for scripts that aren’t needed immediately

Common Mistakes

  1. Oversized hero images: A 4MB PNG hero destroys LCP; use responsive images with srcset
  2. Render-blocking third parties: Google Fonts loaded synchronously delays first paint
  3. No resource hints: preload, prefetch, and preconnect are free performance wins
  4. Hydrating everything: Islands architecture (Astro, Fresh) ships zero JS for static content
  5. Ignoring mobile: 70% of users are on mobile; test on real devices, not just DevTools

Troubleshooting

  • Largest Contentful Paint is high: optimize images, preload critical resources, and reduce server response time.
  • JavaScript bundle size grows: analyze the bundle, split code by route, and tree-shake unused dependencies. Lazy-load non-critical components.
  • Cache hit rate is low: review cache keys, TTLs, and invalidation patterns.
  • Database CPU spikes: find the top queries by execution time and frequency. Add indexes, rewrite queries, or cache results.
  • Throughput drops under load: profile for contention, garbage collection, and blocked threads. Scale horizontally only after optimizing the hot path.

Further Reading

  • Official documentation: check the current reference for the framework or tool used.
  • Related guides: explore the web-performance and performance guides for deeper coverage.
  • Complementary patterns: review design patterns applicable to your technology stack.
  • Public postmortems: study real incidents from teams that faced similar production issues.

Production Notes

  • Deploy gradually using canary or blue-green to catch regressions early.
  • Configure alerts for error rate, p99 latency, and failure rate before enabling in production.
  • Document the rollback in the runbook; test the procedure in staging at least once per quarter.
  • Review structured logs with correlation IDs to trace requests end-to-end during incidents.

Key Takeaways

  • Apply web performance optimization when you need a practical solution for your use case.
  • Monitor performance after implementation; measure latency, errors, and resource usage before and after.
  • Check the Troubleshooting section for common failures; most have documented root causes with fixes.
  • Keep dependencies updated and run tests in CI to prevent production regressions.

See Also


Last updated: 2026-07-09

Common Production Pitfalls

  • Copying the example without adapting it to real data volumes and failure modes.
  • Skipping load and error-injection tests before the first production deployment.
  • Hard-coding values that should be configurable per environment.
  • Forgetting to add logging and monitoring at each step.
  • Deploying without a rollback plan or a tested backup strategy.
  • Assuming the minimal example will scale without adding caching or batching.
  • Not documenting the version and configuration used in production.
  • Letting the recipe sit unchanged when dependencies or scale evolve.

Frequently Asked Questions

What's the single biggest performance win?

Image optimization. Images are typically 60-80% of page weight. Use modern formats, responsive sizing, and lazy loading.

Should I use a CDN?

Yes. A CDN reduces TTFB by serving from edge locations close to users. Essential for global audiences.

How do I balance performance with developer experience?

Use frameworks that optimize by default (Astro, SvelteKit, Next.js with App Router). Don't fight the tooling.