Next.js Core Web Vitals Optimization Checklist: LCP, CLS, and INP
A complete checklist for optimizing Core Web Vitals in Next.js apps. Covers next/image, next/font, script loading strategy, server components, and caching for LCP, CLS, and INP.
Next.js ships with more built-in performance optimisation than almost any other framework. The image component, the font module, the script component, React Server Components — all of them exist specifically to help you hit good Core Web Vitals scores. The problem is that the defaults are not always enough, and the framework does not prevent you from making decisions that undermine your scores. This checklist covers what to check, what to fix, and what to verify in a Next.js application targeting good LCP, CLS, and INP.
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.
Replace every img tag in your application with the Image component from next/image. For third-party images, configure remotePatterns in next.config.js to allow the domain.
priority prop on the hero image
The Image component lazy-loads by default. For your above-the-fold hero image, add the priority prop. This disables lazy loading and adds a preload link to the document head automatically. One image per page — the one most likely to be the LCP element.
priority
Do not add priority to every image. It defeats the purpose.
Self-host fonts with next/font
The next/font module downloads your chosen font at build time and hosts it alongside your static assets. No cross-origin font request at runtime. It also automatically calculates and injects fallback font metric overrides to prevent CLS from font swaps.
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.
next/font prevents font swap CLS automatically
If you are already using next/font, fallback font metrics are injected automatically. Confirm in your CSS output that you see size-adjust and ascent-override values on the fallback font-face declaration.
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 your Client Components. Move any component that does not require interactivity (event listeners, hooks, browser APIs) to a Server Component. This is the highest-leverage INP fix in Next.js.
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: blocks page render. Use only for scripts that must be available before any JS runs.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 - HTML responses cached at CDN with short TTL or ISR
- 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.
Set up a Lighthouse CI integration in your CI pipeline to catch regressions on every pull request. It takes about 20 minutes to configure and saves significant debugging time on performance regressions.
If your Next.js app has persistent Core Web Vitals issues despite applying these steps, the problem is often architectural: too much client-side JavaScript, poor server caching, or unoptimised data fetching patterns. Clixo builds and optimises Next.js applications — talk to us if you need a deeper performance review.