Next.js Core Web Vitals: how we consistently hit 100 on Lighthouse
LCP under 1.2s, INP under 100ms, CLS of zero — consistently, in CI, before the client ever sees the page. The concrete techniques behind a perfect Lighthouse score on a production Next.js App Router site.
A perfect Lighthouse score in a lab is table stakes. The harder problem is a perfect score under real-world conditions: authenticated users, third-party scripts, images from a CMS, fonts loaded from a CDN, and a CI gate that fails the build when you regress. Here is the full technique stack we apply to every Next.js App Router project.
LCP: the hero must be HTML, not JavaScript
The single biggest LCP mistake in Next.js is a hero image inside a component that is lazy-loaded or client-side rendered. The browser cannot start fetching the LCP image until it has parsed and executed the JavaScript — which can be 400ms on a throttled mobile device. The fix is structural: the hero must be in a Server Component, the image must use next/image with priority={true}, and the image URL must be predictable at build time.
// ✅ Server Component hero with priority LCP image
import Image from "next/image";
export default function HeroSection() {
return (
<section className="relative overflow-hidden">
<Image
src="/assets/hero-banner.webp"
alt="Production Engineering Showcase"
width={1440}
height={820}
priority
sizes="(max-width: 768px) 100vw, 1200px"
quality={85}
/>
</section>
);
}INP: interaction budget is 100ms, not 200ms
Google's threshold for Interaction to Next Paint is 200ms, but real-world P75 at that threshold leaves 25% of users with noticeable delay. We target 100ms P75. The main culprits: synchronous state updates on click, large component trees re-rendering on every keypress, and blocking third-party tag managers.
// Defer non-critical scripts past hydration
import Script from "next/script";
export default function AnalyticsScripts() {
return (
<>
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"
strategy="afterInteractive"
/>
<Script src="/support-chat.js" strategy="lazyOnload" />
</>
);
}CLS: zero layout shift discipline
Every layout shift has a concrete cause: images without explicit width and height, fonts swapping after render, and dynamically injected notification banners. Adding font-display: optional for secondary weights and ensuring all media containers define explicit aspect ratios completely eliminates CLS.
The score is a lagging indicator. The practice is structural: hero in a Server Component, images with priority and aspect ratio, fonts with display:swap, scripts deferred, and a CI gate that fails on regression. Build the practice, the score follows.
Field notes authored by the core engineering and systems team at Chimbuk. We build and maintain production Flutter mobile applications, Next.js web architectures, and cloud backends. No recycled marketing theory — only architecture decisions, code patterns, and performance budgets from real production deployments.
Continue Reading
Related Engineering Guides
We architect and build the systems we write about.
From vector search pipelines to high-concurrency payment gateways — schedule a technical discovery call.
