A Practical Guide to Core Web Vitals
The techniques that moved a real e-commerce storefront from a 54 to a 91 Lighthouse score — fonts, images, render blocking, and LCP.
What the scores actually measure
Core Web Vitals are three metrics: LCP (Largest Contentful Paint — how fast the page looks loaded), CLS (Cumulative Layout Shift — how stable the layout is), and INP (Interaction to Next Paint — how responsive the page is to input).
A Lighthouse score is a weighted blend of these and a few secondary metrics. But in practice, fixing LCP and CLS accounts for 80% of the score improvement on most sites.
LCP: the image problem
The LCP element is almost always the hero image or the largest text block above the fold. For images, the fix is almost always the same: use a modern format (WebP or AVIF), preload the LCP image, and size it correctly so the browser doesn't download a 3000px image for a 400px slot.
// Preload the LCP image in Next.js
<Image
src="/hero.jpg"
priority // adds <link rel="preload">
sizes="100vw"
fill
alt="Hero image"
/>LCP: the font problem
Web fonts block the LCP text from rendering. The solution is to preconnect to the font provider, use font-display: swap, and — if possible — self-host the font so you control the cache headers.
- Add <link rel="preconnect"> for the font origin
- Use font-display: optional if layout shift from swap is visible
- Self-host via next/font/google which inlines the @font-face at build time
- Subset the font to only the characters you use
CLS: reserve space for async content
Layout shifts happen when content loads asynchronously and pushes other elements around. The fix is to always reserve space before the content arrives — set explicit width and height on images, use min-height on ad slots, and avoid inserting content above the fold after load.
The render-blocking JS problem
Third-party scripts (analytics, chat widgets, A/B testing) are the biggest culprit for render-blocking. Load them with strategy="lazyOnload" in Next.js Script, or defer them until after the first user interaction.
Performance is not a one-time audit. It's a constraint you design around from the start. Adding it later is an order of magnitude more expensive than building it in.