Next.js Core Web Vitals Optimization Checklist: LCP, CLS, and INP
Review LCP, CLS, and INP in Next.js, including image loading, fonts, scripts, component boundaries, and caching.
Next.js includes image, font, script-loading, and rendering tools that can affect Core Web Vitals. Measure LCP, CLS, and INP first, then use this checklist to investigate the components and resources behind poor results.
Before You Start: Measure First
Do not guess. Run your app through:
- PageSpeed Insights with your production URL for field data from real users
- Lighthouse in Chrome DevTools for lab data and specific recommendations
- WebPageTest for waterfall analysis and filmstrip rendering
Field data (from CrUX, shown in PageSpeed Insights) represents real users on real devices. Lab data (Lighthouse) is a simulated load. Both matter. Fix field data failures first.
Next.js Core Web Vitals Checklist
LCP: Largest Contentful Paint
next/image for every image
The next/image component handles format conversion to WebP/AVIF, lazy loading by default, and serves the correct size via srcset. If you have raw img tags in your JSX, you are opting out of all of this.
Evaluate next/image for images that benefit from its sizing and optimization. For remote sources, configure narrowly scoped remotePatterns in next.config.js.
Load the LCP image promptly
The example below uses the older priority prop. Next.js 16 deprecates it in favor of preload; current guidance also recommends considering loading="eager" or fetchPriority="high" for the image. Choose based on the measured LCP resource. Next.js Image reference.
priority
Prioritize the measured LCP image rather than every image on the page.
Self-host fonts with next/font
next/font/google downloads fonts at build time and serves them with the application’s assets, avoiding browser requests to Google. Automatic fallback adjustment can reduce font-related layout shift; check the chosen font and configuration rather than assuming all shifts are eliminated.
Replace Google Fonts link tags in your _document.tsx or layout with next/font/google imports:
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });Apply the font className to your root layout element.
Verify TTFB with React Server Components
App Router Server Components render on the server and stream HTML to the client. This reduces JavaScript bundle size (Server Components have zero client-side JavaScript) and can improve TTFB. Move data-fetching logic into Server Components and keep Client Components for interactive UI only.
Enable Incremental Static Regeneration or static rendering where possible
Statically rendered pages are served from CDN cache. TTFB for a cached static page is typically under 50ms. Dynamic server rendering adds server computation time. Use generateStaticParams and static rendering for pages where content does not change per-request.
CLS: Cumulative Layout Shift
Always set width and height on next/image
Next.js Image component requires width and height props (or fill layout). These are used to reserve space before the image loads. If you get a TypeScript error for missing dimensions, that is the framework protecting you from CLS.
For images with unknown dimensions at build time, use fill with a positioned container that has an explicit aspect ratio set via CSS.
Do not use dynamic content that inserts above existing content
Cookie banners, notification bars, and chat widgets that render after hydration are common CLS sources in Next.js apps. Options:
- Render them server-side so they are part of the initial HTML
- Reserve their height with a placeholder before they appear
- Use
position: fixedso they do not affect document flow
Check for hydration-related layout shifts
If a component renders differently on the server versus the client — for example, because it reads from localStorage or a browser API — Next.js will hydrate it and the DOM difference can cause a layout shift. Use suppressHydrationWarning only when truly necessary, and prefer conditional rendering patterns that produce matching server and client output.
Verify font fallback metrics
Check whether automatic fallback adjustment is enabled for the selected font. Inspect generated CSS and measure layout shift while fonts load. See the font reference.
INP: Interaction to Next Paint
Minimise Client Components
Every Client Component adds JavaScript to the bundle that executes on the main thread. Large client bundles lead to long parse-and-compile tasks that block the main thread and inflate input delay.
Audit Client Components for code that can run on the server, then remeasure the interaction. Bundle reduction may help, but the profile determines whether it addresses the INP bottleneck.
Code-split with dynamic imports
Use next/dynamic for heavy Client Components that are not needed on the initial render — modal dialogs, rich text editors, chart libraries, complex forms. Dynamic imports split those components into separate chunks that are only loaded when needed.
const RichEditor = dynamic(() => import('./RichEditor'), { ssr: false });Audit third-party scripts with next/script
The next/script component accepts a strategy prop:
beforeInteractive: loads before Next.js code and hydration; execution does not block hydration. Reserve it for site-wide scripts that need this early loading. Next.js Script reference.afterInteractive(default): loads after hydration. Appropriate for tag managers and analytics.lazyOnload: loads during browser idle time. Good for chat widgets, low-priority scripts.
Move every third-party script from a raw script tag to next/script with the appropriate strategy. Most marketing tags belong in afterInteractive or lazyOnload.
Profile interactions with Chrome DevTools
Record a performance trace while clicking your most-used interactive elements. Look for input delay caused by long tasks. Common Next.js-specific culprits: large React re-renders triggered by global state updates, heavy useEffect chains on user interaction, and unoptimised data mutations that re-render large component trees.
Use React DevTools Profiler to identify which components are causing slow re-renders.
Caching and Infrastructure
- Static assets (JS, CSS, images) served with
Cache-Control: public, max-age=31536000, immutable - Cache eligible public HTML with a deliberate TTL or ISR policy; do not share user-specific responses between users
- Deployed on a CDN-first platform (Vercel, Cloudflare Pages, or equivalent)
- Core Web Vitals monitored in production with a RUM tool or Search Console
Validate and Iterate
After applying fixes, redeploy and re-run PageSpeed Insights. Allow 28 days for CrUX field data to update — it reflects a rolling 28-day window of real user data.
Use Lighthouse CI to check for performance regressions in pull requests. Compare those lab checks with production field data.