Core Web Vitals — the three metrics that matter
Google measures user-perceived speed with three metrics. All three feed into rankings and all three are visible in your Search Console:
- LCP (Largest Contentful Paint) — how long until the largest above-the-fold element paints. Target: under 2.5s.
- CLS (Cumulative Layout Shift) — how much the page jumps around while loading. Target: under 0.1.
- INP (Interaction to Next Paint) — how quickly the page reacts to a tap or click. Target: under 200ms.
Fixing LCP
LCP is almost always dominated by one element: a hero image, a hero heading, or a hero video. Optimizations, in order of impact:
- Serve the hero image in a modern format (AVIF or WebP), correctly sized for the viewport.
- Preload the hero image with <link rel="preload" as="image">.
- Never lazy-load the hero. Lazy-load everything below the fold.
- Inline the CSS needed for the first paint; defer the rest.
- Cut render-blocking JS out of the head. If you can't remove a script, mark it defer or async.
Fixing CLS
CLS is caused by content moving after it has painted. The three usual culprits: images without dimensions, web fonts that swap, and injected iframes/ads.
- Always set width and height (or aspect-ratio) on <img> and <iframe>.
- Preload critical fonts and use font-display: optional or a size-adjust @font-face fallback.
- Reserve space for banners, cookie notices, and embeds with a fixed min-height.
Fixing INP
INP replaces FID as of March 2024. It measures the actual latency of interactions, not just the first one, and it is brutally honest about JavaScript-heavy sites.
- Break long tasks. Anything over 50ms on the main thread degrades INP.
- Defer non-critical third-party scripts (chat widgets, analytics, tag managers) until after user interaction.
- Use requestIdleCallback for background work.
- Prefer CSS transitions and CSS view transitions over JS animation libraries.
Fonts
Web fonts are the most commonly-mishandled asset on small business sites. Self-host them, preload the two or three weights you actually use, subset to Latin unless you need more, and always define a font-family fallback stack that visually matches so swap-in is invisible.
Images
- AVIF for photos when browser support allows, WebP as fallback, JPG only as last resort.
- Generate a srcset with 2–3 widths (e.g., 640, 1024, 1600) and use sizes to tell the browser which to pick.
- Compress aggressively — mozjpeg quality 75 or AVIF quality 60 is almost always indistinguishable.
- Lazy-load everything below the fold with loading="lazy" and decoding="async".
Compression, caching, and CDNs
Brotli compression is table stakes for HTML, CSS, and JS. HTTP caching headers should be aggressive on hashed assets (Cache-Control: public, max-age=31536000, immutable) and shorter on HTML (max-age=0, must-revalidate). A CDN is not optional for a business that serves customers across a region — it moves the bytes physically closer to the visitor and terminates TLS at the edge.
JavaScript and CSS discipline
- Ship less JS. Every KB of client-side JS is a tax on INP and LCP.
- Server-render as much as you can. Hydrate only what needs interactivity.
- Route-split — never load the shopping cart JS on the About page.
- Purge unused CSS (Tailwind's JIT and Lightning CSS do this automatically).
- Kill jQuery, Bootstrap JS, and legacy carousel libraries. Native equivalents are lighter and faster.
The speed optimization checklist
- Run Lighthouse and PageSpeed Insights on a real mobile profile.
- Identify the LCP element and preload it.
- Serve hero images as AVIF or WebP with srcset.
- Set explicit dimensions on every image and iframe.
- Self-host and preload critical fonts.
- Defer or async every non-critical script.
- Enable Brotli compression at the edge.
- Set long cache headers on hashed assets.
- Deploy behind a global CDN.
- Retest on a real device — not just DevTools.