# 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.

- **Published:** 2025-07-11
- **Author:** Clixo
- **Reading time:** 6 min read
- **Tags:** nextjs, core-web-vitals, web-performance, react
- **Canonical URL:** https://clixo.sh/blog/nextjs-core-web-vitals-optimization-checklist

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.

```mermaid
flowchart LR
  A["PageSpeed Insights"] --> D{"Failing metric?"}
  B["Lighthouse DevTools"] --> D
  C["WebPageTest waterfall"] --> D
  D -->|LCP| E["next/image priority + self-hosted fonts + RSC"]
  D -->|CLS| F["Set image dimensions + reserve dynamic space"]
  D -->|INP| G["Minimize Client Components + code-split + next/script"]
  E --> H["Deploy and retest"]
  F --> H
  G --> H
```

## 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:

```javascript
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: fixed` so 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.

```javascript
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](https://clixo.sh/#contact) — talk to us if you need a deeper performance review.

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
