Frontend · Guide

Web Performance

Core Web Vitals, bundle size and code splitting, and the image and font decisions that move the numbers most.

— min read Frontend

Measure, Then Fix

Optimising without measuring is guessing. The bottleneck is almost never where it feels like it is, and the fix that mattered is usually one of three or four things — not the hundred you could do.

Two kinds of data. Lab — Lighthouse on your machine — is repeatable and good for comparing before and after. Field — real users on real devices and networks — is the truth, and it is always worse than the lab.

Test on a mid-range phone over a throttled connection. A desktop on office wifi will tell you everything is fine, right up until it is not for the people actually using it.

Core Web Vitals

Three metrics, each standing for a different question a user is asking without thinking about it.

MetricAsksGoodUsually caused by
LCP
Largest Contentful Paint
Has the main thing appeared?Under 2.5sA slow server, or a huge hero image
INP
Interaction to Next Paint
Does it respond when I touch it?Under 200msLong JavaScript tasks blocking the main thread
CLS
Cumulative Layout Shift
Does it stay still?Under 0.1Images and ads with no reserved space

CLS is the cheapest to fix and the most infuriating to experience — the tap that lands on the wrong thing because content moved. Set width and height on every image, reserve space for anything injected, and never insert content above what someone is already reading.

INP is about the main thread. JavaScript is single-threaded, so a long task means nothing else can happen — not a click, not a keystroke, not a repaint. Break long work up, or move it off the main thread.

Chase the field data, not the Lighthouse score. A perfect lab score on a fast laptop says nothing about a four-year-old phone on mobile data, which is what a large share of your users actually have.

Bundle Size & Code Splitting

JavaScript is the most expensive thing you ship, byte for byte. An image of the same size decodes off the main thread; JavaScript must be downloaded, parsed, compiled and executed before it does anything.

TechniqueEffect
Route-based splittingLoad only the current page's code
Lazy-load heavy componentsCharts and editors arrive when opened
Audit before adding a dependencyA date library can outweigh your app
Prefer platform APIsIntl and fetch cost nothing to ship
Defer third-party scriptsAnalytics should never block first paint

Run a bundle analyser before optimising anything. It routinely shows one dependency accounting for a third of the bundle, and often one nobody remembers adding.

Third-party scripts are the most common cause of a fast site being slow, and the easiest to overlook because they are not in your repository. Every tag manager, chat widget and analytics snippet runs on your main thread.

Images & Fonts

Images are usually most of a page's bytes, so the decisions here move LCP more than almost anything else in your control.

DoWhy
Serve modern formatsFar smaller than JPEG at the same quality
Offer sizes with srcsetA phone should not download a desktop image
Set width and heightReserves space, so no layout shift
loading="lazy" below the foldDo not spend bandwidth on unseen images
Never lazy-load the heroIt is your LCP element — prioritise it

Fonts cause the other visible problem. A custom font that has not arrived yet leaves text either invisible or in a fallback that reflows when it swaps.

TechniqueEffect
font-display: swapShow fallback text immediately
Preload the critical fontStarts the download sooner
Subset the character setDrops glyphs you never use
Match fallback metricsReduces the reflow when it swaps
Self-hostRemoves a third-party connection
The fastest asset is the one you do not send. Before optimising an image, ask whether the page needs it — and before adding a font, whether a system font would do.

Interview Questions

What are the Core Web Vitals?

LCP, how long until the main content appears, good under 2.5s. INP, how quickly the page responds to interaction, good under 200ms. CLS, how much the layout moves, good under 0.1.

How do you fix layout shift?

Reserve space before content arrives — width and height on images, fixed dimensions for embeds and ads, and never inserting content above something the user is already reading.

Why is JavaScript more expensive than an image of the same size?

An image is decoded off the main thread. JavaScript must be downloaded, parsed, compiled and executed on the main thread, blocking everything else while it runs.

Lab data looks great, field data is poor. Why?

Lab is a fast machine on a fast network with no extensions and a warm cache. Field is real devices, real networks and real third-party scripts. Field is the truth; lab is for comparing changes.

Which image should never be lazy-loaded?

The one above the fold, usually the hero — it is typically the LCP element, and deferring it directly delays the metric you are trying to improve.

What does font-display: swap do?

Renders text in a fallback font immediately rather than leaving it invisible while the custom font loads. Text is readable sooner, at the cost of a reflow when the real font arrives — which matching fallback metrics reduces.

Quick Quiz

1. CLS measures…
2. The hero image should…
3. Poor INP usually means…
4. Which data should you trust?
5. Byte for byte, the most expensive thing to ship is…