Optimize Core Web Vitals with Next.js: the complete technical guide (2026)
Introduction
In 2026, Core Web Vitals are no longer just a Google recommendation — they're a confirmed ranking factor that directly impacts your visibility. According to Chrome UX Report data, 53% of French websites still don't pass the "good" threshold on all three key metrics.
If you're using Next.js (and you should — it's the most performant React framework for SEO), you have a significant advantage. But you need to know how to leverage it. In this guide, I'll show you concretely how to optimize each metric with copy-paste-ready code.
The 3 metrics that matter in 2026
Quick recap:
- LCP (Largest Contentful Paint): loading time of the largest visible element. Target: < 2.5 seconds.
- CLS (Cumulative Layout Shift): visual stability. Target: < 0.1.
- INP (Interaction to Next Paint): interaction responsiveness. Target: < 200ms. INP replaced FID since March 2024.
Each of these metrics influences your Google ranking. Let's see how to optimize them one by one.
1. Optimize LCP: load what matters first
LCP is often dragged down by unoptimized images or render-blocking fonts. With Next.js, here's the solution.
Use the Next.js Image component
The next/image component automatically handles lazy loading, resizing, and WebP/AVIF format:
`tsx
import Image from "next/image";
export default function Hero() {
return (
src="/images/hero-banner.jpg" alt="Freelance web developer at work" fill priority // ← Disables lazy loading for the LCP image sizes="100vw" className="object-cover" quality={85} />
Your website, optimized for performance
);
}
`
Key points:
prioritytells Next.js to preload this image (automatically adds a).sizes="100vw"lets the browser choose the right image size for the screen.quality={85}reduces weight without visible loss.
Preload your fonts with next/font
Google Fonts often block rendering. Next.js solves this natively:
`tsx
// app/layout.tsx
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap", // ← Shows text immediately
preload: true,
variable: "--font-inter",
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
`
Result: 0ms of font-blocking, because Next.js self-hosts them automatically and applies font-display: swap.
Pro tip: Server Components for above-the-fold content
With the Next.js App Router, your components are Server Components by default. HTML is generated server-side and sent immediately:
`tsx
// app/page.tsx — Server Component by default, no "use client"
async function getHeroData() {
// This request runs server-side at build time
const data = await fetch("https://api.example.com/hero", {
next: { revalidate: 3600 }, // ISR: regenerates every hour
});
return data.json();
}
export default async function Home() {
const hero = await getHeroData();
return (
{hero.title}
{hero.subtitle}
);
}
`
No client-side JavaScript for initial render = drastically reduced LCP.
2. Eliminate CLS: every pixel in its place
CLS is caused by elements that "shift" during loading. The usual culprits: images without dimensions, fonts that change size, and dynamically injected content.
Reserve space for images
`tsx
// ❌ Bad: no dimensions = guaranteed CLS

// ✅ Good: explicit dimensions + CSS aspect-ratio
src="/photo.jpg" alt="Project photo" width={800} height={450} className="aspect-video w-full h-auto" /> The When a component loads client-side data, use import { Suspense } from "react"; function TestimonialSkeleton() { return ( `next/image component automatically calculates the ratio and reserves space. CLS: 0.Handle dynamic content with Suspense
Suspense with an identically-sized skeleton:`tsx
);
}
export default function Page() {
return (
What our clients say
);
}
`
The skeleton has exactly the same height as the final component. Result: zero visual shift.
The golden rule for ads and embeds
If you integrate iframes (YouTube, Google Maps, etc.), always define a container with fixed dimensions:
`tsx
`
3. Tame INP: ultra-responsive interactions
INP measures the time between a user interaction (click, tap, keystroke) and the visual update. It's the hardest metric to optimize.
Use useTransition for non-urgent updates
`tsx
"use client";
import { useState, useTransition } from "react";
export default function SearchFilter({ items }: { items: Item[] }) {
const [query, setQuery] = useState("");
const [filtered, setFiltered] = useState(items);
const [isPending, startTransition] = useTransition();
function handleSearch(value: string) {
setQuery(value); // ← Urgent update (responsive input)
startTransition(() => {
// ← Non-urgent update (filtering can wait)
const results = items.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase())
);
setFiltered(results);
});
}
return (
type="text"
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Search..."
className="w-full rounded border p-3"
/>
{filtered.map((item) => (
))}
);
}
`
useTransition tells React: "the input is priority, filtering can happen in the background". The user feels zero lag while typing.
Load heavy components dynamically
`tsx
import dynamic from "next/dynamic";
// The map component only loads when it's visible
const Map = dynamic(() => import("@/components/Map"), {
loading: () => (
),
ssr: false, // No server render for heavy interactive components
});
export default function ContactPage() {
return (
Find us
);
}
`
4. Measure your results: the monitoring setup
Optimizing without measuring is shooting blind. Here's how to track your Core Web Vitals in production with Next.js:
`tsx
// app/layout.tsx
import { SpeedInsights } from "@vercel/speed-insights/next";
import { Analytics } from "@vercel/analytics/react";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
`
If you're not on Vercel, use the web-vitals API directly:
`tsx
// app/components/WebVitals.tsx
"use client";
import { useReportWebVitals } from "next/web-vitals";
export function WebVitals() {
useReportWebVitals((metric) => {
// Send to your analytics (Google Analytics, Plausible, etc.)
console.log(metric.name, metric.value);
if (typeof window.gtag === "function") {
window.gtag("event", metric.name, {
value: Math.round(metric.value),
event_label: metric.id,
non_interaction: true,
});
}
});
return null;
}
`
Summary checklist
Before going to production, verify:
- ✅ LCP images with
priorityandsizesdefined - ✅ Fonts loaded via
next/fontwithdisplay: swap - ✅ Server Components for all above-the-fold content
- ✅ Explicit dimensions on all images and iframes
- ✅
Suspense+ skeletons for dynamic content - ✅
useTransitionfor filtering/search interactions - ✅
dynamic()for heavy non-critical components - ✅ Production monitoring enabled
Conclusion
Core Web Vitals aren't a technical chore — they're a competitive advantage. A site that loads in under 2 seconds, doesn't shift, and responds instantly to clicks converts 2 to 3 times better than a slow site (source: Google/Deloitte, 2024).
With Next.js and the techniques in this guide, you can reach a Lighthouse score of 95-100 without sacrificing design or features.
Need a developer who masters these optimizations? I build ultra-performant Next.js sites for my clients. Book a call to discuss — the initial performance audit is free.