Speed Is a Feature
A one-second delay in page load time leads to 11% fewer page views, a 16% decrease in customer satisfaction, and a 7% drop in conversions. Those aren't hypothetical numbers—they come from years of research by Google, Amazon, and Walmart.
Google has made this even more concrete by incorporating Core Web Vitals into its ranking algorithm. Your site's performance directly affects where you show up in search results. Let's break down what these metrics are, how to measure them, and most importantly, how to fix them.
Understanding Core Web Vitals
Google defines three Core Web Vitals that measure real-world user experience:
Largest Contentful Paint (LCP)
What it measures: How long it takes for the largest visible content element (usually a hero image or heading) to fully render.
Target: Under 2.5 seconds
Why it matters: LCP represents the moment a user perceives the page as "loaded." A slow LCP feels like staring at a blank screen.
Common causes of poor LCP:
- Unoptimized hero images served in large file sizes
- Render-blocking CSS and JavaScript in the document head
- Slow server response times (high TTFB)
- Client-side rendering that delays content display
Interaction to Next Paint (INP)
What it measures: The responsiveness of a page to user interactions throughout its entire lifecycle. INP replaced First Input Delay (FID) as a Core Web Vital.
Target: Under 200 milliseconds
Why it matters: INP captures how snappy your site feels when users click buttons, type in forms, or interact with any element. A high INP means the site feels sluggish.
Common causes of poor INP:
- Long-running JavaScript tasks blocking the main thread
- Heavy event handlers that trigger layout recalculations
- Excessive DOM size slowing down updates
- Third-party scripts competing for processing time
Cumulative Layout Shift (CLS)
What it measures: How much the page layout shifts unexpectedly while content loads.
Target: Under 0.1
Why it matters: Nothing frustrates users more than clicking a button only to have the page shift and cause them to tap something else entirely. CLS quantifies this instability.
Common causes of poor CLS:
- Images without explicit width and height attributes
- Ads, embeds, or iframes injected without reserved space
- Web fonts causing text to reflow when they load (FOIT/FOUT)
- Dynamically injected content above existing elements
How to Measure Performance
Lab Tools (Development)
Use these during development to catch issues early:
- Lighthouse — built into Chrome DevTools, provides scores and specific recommendations
- PageSpeed Insights — combines lab and field data with actionable advice
- WebPageTest — detailed waterfall charts, filmstrip views, and multi-location testing
Field Data (Real Users)
Lab tools test under controlled conditions. Field data shows what real users experience:
- Chrome User Experience Report (CrUX) — aggregated real-user data from Chrome browsers
- Google Search Console — Core Web Vitals report showing which URLs pass or fail
- Web Vitals JavaScript library — capture metrics directly in your analytics
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(metric => sendToAnalytics('LCP', metric));
onINP(metric => sendToAnalytics('INP', metric));
onCLS(metric => sendToAnalytics('CLS', metric));
Optimization Strategies
Fixing LCP
Optimize images — the most common LCP culprit:
<!-- Use modern formats with fallbacks -->
<picture>
<source srcset="/hero.avif" type="image/avif" />
<source srcset="/hero.webp" type="image/webp" />
<img src="/hero.jpg" alt="Hero" width="1200" height="630"
fetchpriority="high" />
</picture>
In Next.js, the Image component handles this automatically:
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={630}
priority // Preloads the LCP image
/>
Other LCP optimizations:
- Preload critical resources with
<link rel="preload"> - Use a CDN to reduce server response time
- Inline critical CSS to avoid render-blocking stylesheets
- Server-side render above-the-fold content
Fixing INP
Break up long tasks:
// Bad: One long blocking task
function processLargeDataset(items) {
items.forEach(item => heavyComputation(item));
}
// Good: Yield to the main thread between chunks
async function processLargeDataset(items) {
const chunks = chunkArray(items, 50);
for (const chunk of chunks) {
chunk.forEach(item => heavyComputation(item));
await scheduler.yield(); // Let the browser handle user input
}
}
Other INP optimizations:
- Debounce input handlers (search fields, scroll events)
- Use
requestAnimationFramefor visual updates - Move heavy computation to Web Workers
- Reduce DOM size — aim for under 1,500 elements
- Lazy-load third-party scripts that aren't needed immediately
Fixing CLS
Always set dimensions on media elements:
<!-- Bad: No dimensions — causes layout shift when image loads -->
<img src="/photo.jpg" alt="Photo" />
<!-- Good: Browser reserves space before the image loads -->
<img src="/photo.jpg" alt="Photo" width="800" height="600" />
Handle web fonts properly:
/* Use font-display: swap to prevent invisible text */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
}
/* Better: Use size-adjust to minimize reflow */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
size-adjust: 105%;
}
Other CLS optimizations:
- Reserve space for ads and embeds with
min-heightoraspect-ratio - Use CSS
containproperty to isolate layout changes - Avoid inserting content above existing elements after page load
- Use
transformanimations instead of properties that trigger layout (top, left, width, height)
Performance Budget
Set clear targets and hold your team accountable:
| Metric | Good | Needs Work | Poor |
|---|---|---|---|
| LCP | < 2.5s | 2.5s - 4s | > 4s |
| INP | < 200ms | 200ms - 500ms | > 500ms |
| CLS | < 0.1 | 0.1 - 0.25 | > 0.25 |
| Total JS | < 200KB | 200-400KB | > 400KB |
| Total page weight | < 1.5MB | 1.5-3MB | > 3MB |
Automate enforcement in your CI/CD pipeline:
# Run Lighthouse CI in your build pipeline
npx @lhci/cli autorun --config=lighthouserc.json
Quick Wins Checklist
If you're short on time, these changes deliver the biggest impact with the least effort:
- Enable text compression (Gzip/Brotli) on your server
- Add
widthandheightto all<img>and<video>elements - Set
fetchpriority="high"on your LCP image - Defer non-critical JavaScript with
asyncordefer - Self-host fonts instead of loading from Google Fonts
- Preconnect to required third-party origins
- Use
loading="lazy"on below-the-fold images
Conclusion
Web performance isn't a one-time fix — it's an ongoing discipline. The sites that rank highest and convert best are the ones that continuously measure, optimize, and guard against regressions.
Start with Core Web Vitals as your baseline, measure with real user data, and tackle the biggest bottlenecks first. Even small improvements compound into significantly better user experience and business outcomes.
Need help diagnosing and fixing performance issues on your website? Reach out to our team for a free performance audit.



