Web Performance

Web Performance

Performance is a feature. Core Web Vitals, lazy loading, smaller bundles and smarter caching decide whether users stay or leave before your page even renders.

intermediate15 min readUpdated Sep 15, 2026
vitals.js
js
// vitals.js
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log("LCP:", Math.round(entry.startTime));
  }
}).observe({
  type: "largest-contentful-paint",
  buffered: true,
});
Core metrics
LCP, CLS, INP
Field data
Real user monitoring
Lab data
Lighthouse and DevTools
Biggest lever
Ship less JavaScript
Images
Usually the heaviest asset
Budget
Set a limit and enforce it

Why it matters

Why performance is a feature

Better metrics

Faster pages score better on Core Web Vitals, which affects both user experience and search ranking.

Happier users

Every 100ms of delay measurably reduces engagement and conversion. Speed is not cosmetic.

Cheaper infrastructure

Smaller payloads and smarter caching reduce bandwidth and server load as traffic grows.

The big picture

The three levers of performance

Load less, load it later, and make the browser's work cheap. Almost every optimisation falls into one of those.

Loading

Get it there

Reduce bytes, split bundles, lazy-load below-the-fold content and prioritise what matters.

Rendering

Show it fast

Minimise layout shifts, avoid blocking work and keep the main thread free.

Runtime

Stay smooth

Keep interactions responsive by shipping less JavaScript and breaking up long tasks.

Performance at a glance

The metrics and tools

LCP

Largest Contentful Paint measures when the main content is visible.

INP

Interaction to Next Paint measures responsiveness to user input.

CLS

Cumulative Layout Shift measures visual stability.

TTFB

Time to First Byte reflects server and network latency.

Performance API

Measure real timings in the browser with observers.

Bundle budget

Cap the JavaScript you ship and fail the build when it grows.

A short history

From page-load time to user-centric metrics

  1. 2010

    Page load time

    Early performance work focuses on how long the page takes to load.

    10
  2. 2015

    RAIL model

    Google reframes performance around response, animation, idle and load.

    15
  3. 2020

    Core Web Vitals

    LCP, CLS and FID give the industry shared user-centric metrics.

    20
  4. 2024

    INP replaces FID

    Interaction to Next Paint becomes the responsiveness metric.

    24
  5. Today

    Performance budgets

    Teams treat performance as a build-time constraint, not an afterthought.

    Today

The complete guide

Web Performance: Everything you need to know

Why performance is a feature

Performance is not a polish step at the end of a project. It decides whether users see your content at all. A page that takes four seconds to show its main content loses a large share of visitors before it is usable, and slow interactions make an app feel broken even when it works.

The good news is that performance is measurable and mostly systematic. A small number of levers — load less, load later, do less work on the main thread — account for the vast majority of the wins. This guide covers the metrics, the tools and the techniques that matter most.

Core Web Vitals

Three metrics, collectively called Core Web Vitals, describe the user experience:

  • Largest Contentful Paint (LCP) — when the largest visible element finishes rendering. Good is under 2.5 seconds.
  • Interaction to Next Paint (INP) — the latency between a user interaction and the next visual update. Good is under 200 milliseconds.
  • Cumulative Layout Shift (CLS) — how much visible content moves unexpectedly. Good is under 0.1.

Supporting metrics include Time to First Byte (TTFB) for server latency and First Contentful Paint (FCP) for when anything first appears. Together they tell you whether a page is fast, responsive and stable.

Measuring

You need both field data and lab data.

Field data comes from real users and reflects the true range of devices and networks. It is what you optimise for. Lab data from Lighthouse or DevTools is reproducible, which makes it better for diagnosing a specific problem.

The browser exposes real timings through the Performance API.

// observe.js
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime);
  }
}).observe({ type: "largest-contentful-paint", buffered: true });

Libraries like web-vitals wrap these observers and report to your analytics, which gives you field data without building the plumbing yourself.

Loading less

The single biggest lever is shipping less. JavaScript is the most expensive resource because it must be downloaded, parsed and executed, and it competes with rendering for the main thread.

  • Remove unused dependencies and prefer smaller alternatives.
  • Import only the functions you use from a library.
  • Split routes and heavy features with dynamic import().
  • Render on the server where possible and ship only the interactive parts.
  • Compress and serve modern formats such as Brotli and AVIF.
  • Set a bundle budget and fail CI when it grows.

A bundle analyser shows exactly what is in your build, which is usually surprising the first time you look.

Loading later

Not everything is needed for the first paint. Defer the rest.

<!-- deferred.js -->
<script src="app.js" defer></script>
<script src="analytics.js" async></script>
<img src="hero.avif" fetchpriority="high" alt="Hero" />
<img src="thumb.avif" loading="lazy" decoding="async" width="400" height="300" alt="" />

defer runs scripts after parsing, async runs them as soon as they download, fetchpriority="high" promotes the LCP image, and loading="lazy" defers offscreen images. Dynamic import() does the same for JavaScript modules.

Rendering and stability

Two rendering issues dominate CLS and perceived speed.

Reserve space for images, iframes, ads and embeds so they cannot push content around. Set width and height, or use aspect-ratio in CSS. Use a font strategy that avoids a late swap, such as font-display: swap with a matching fallback or self-hosted fonts.

Keep the main thread free. Long tasks block interaction and inflate INP. Break up expensive work, move heavy computation to a Web Worker, and avoid layout thrashing by batching DOM reads and writes.

Caching

Caching turns repeat visits into instant loads. Fingerprint assets with content hashes and serve them with a long Cache-Control max-age, since a changed file gets a new name. Serve HTML with a short cache and revalidate. For offline and repeat-visit speed, a service worker can cache the app shell and assets, as covered in the PWA guide.

Performance budgets

A budget turns performance into a constraint rather than a hope.

{
  "budgets": [
    { "path": "/*", "resourceSizes": [{ "resourceType": "script", "budget": 170 }] }
  ]
}

Pick a realistic limit for JavaScript, images and LCP, then enforce it in CI. When a pull request exceeds the budget, the build fails and the author decides whether the trade-off is worth it. This is how teams keep performance from eroding over time.

Best practices

  • Measure before optimising, and use both field and lab data.
  • Ship less JavaScript before micro-optimising anything else.
  • Lazy-load below-the-fold images and split heavy routes.
  • Set explicit dimensions on media to protect CLS.
  • Use fetchpriority, defer and async deliberately.
  • Fingerprint assets and cache them for a long time.
  • Set and enforce a bundle budget in CI.

Common mistakes

  • Optimising the wrong thing without measuring.
  • Shipping a huge client bundle for content that could be static.
  • Eagerly loading every image and script.
  • Omitting image dimensions and causing layout shift.
  • Blocking the main thread with long synchronous tasks.
  • Treating performance as a one-off project instead of a continuous budget.

Where to go next

Performance is a discipline, not a checklist. Use Vite to split and analyse your bundle, cut JavaScript weight with the patterns in the React guide, add offline caching with PWA, and keep the main thread free with solid JavaScript. Then measure your own site with field data and pick the one change with the biggest impact.

Loading images

Lazy-load images below the fold and set explicit dimensions. Eager loading and missing sizes hurt LCP and cause layout shift.

Prefer
<img
  src="hero.avif"
  width="1200"
  height="600"
  alt="Hero"
  fetchpriority="high"
/>
<img
  src="thumb.avif"
  width="400"
  height="300"
  loading="lazy"
  alt=""
/>
Avoid
<img src="hero.png" alt="Hero" />
<img src="thumb.png" alt="" />
<!-- no dimensions,
     everything eager -->

Loading scripts

Non-critical scripts should not block HTML parsing. Defer or load them on demand.

Prefer
<script src="app.js" defer></script>
<!-- or load when needed -->
<script type="module">
  import("./heavy.js");
</script>
Avoid
<head>
  <script src="analytics.js"></script>
  <script src="heavy-widget.js"></script>
</head>

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Web Performance?

Our interactive tutorial walks you through Web Performance step by step — with quizzes and real code you can run in the browser.