# Core Web Vitals optimization guide [2026 edition] — improving INP, LCP, and CLS with Next.js to grow SEO and CV

> A practical guide to improving 2026's Core Web Vitals (INP, LCP, CLS) with Next.js. With real code, it explains production-quality techniques: crushing the most failure-prone INP, image/font/SSR optimization to get LCP under 2.5 seconds, dimension reservation to bring CLS near zero, and field measurement (web-vitals / RUM).

- Published: 2026-06-24
- Author: 友田 陽大
- Tags: Next.js, パフォーマンス, フロントエンド, React, アクセシビリティ, アーキテクチャ設計
- URL: https://tomodahinata.com/en/blog/core-web-vitals-nextjs-inp-lcp-cls-optimization-guide
- Category: Frontend
- Pillar guide: https://tomodahinata.com/en/blog/nextjs-16-app-router-cache-components-data-fetching

## Key points

- Core Web Vitals are 3 metrics. The 'good' criteria are LCP under 2.5 seconds, INP under 200ms, CLS under 0.1, judged at the 75th percentile of real users.
- The 2026 battleground is INP. Its essence is the amount and length of JavaScript; reducing JS with Server Components and splitting long tasks works.
- Nail LCP with images, fonts, and SSR. Reliably attach next/image's priority and next/font's display: swap.
- Crush CLS with dimension reservation. Erase shifts of elements appearing later with aspect-ratio and scrollbar-gutter: stable.
- Pass/fail is decided by the field (CrUX / RUM), not the lab (Lighthouse). Measure real-user values yourself with web-vitals.

---

"Fast" isn't an impression but **measurable quality.** This article, premised on Next.js, shows the procedure to improve with numbers, not guesses.

---

## 1. The true identity of the 3 metrics and the passing line

| Metric | What it measures | Good | Needs improvement | Poor |
| ---- | ---------- | ---- | ------ | ---- |
| LCP (Largest Contentful Paint) | Display speed of the main content | Under 2.5 sec | 2.5–4.0 sec | Over 4.0 sec |
| INP (Interaction to Next Paint) | Responsiveness to operations | Under 200ms | 200–500ms | Over 500ms |
| CLS (Cumulative Layout Shift) | Layout shift | Under 0.1 | 0.1–0.25 | Over 0.25 |

There are two important premises.

- **Judged at the 75th percentile.** Unless "75% of visits are good," you don't pass. Even if the average is good, slow-device and slow-network users dragging it down make it fail.
- **Field data is decisive.** What Google looks at is real users (CrUX). Even if your local Lighthouse score is 100, if the field is slow, it fails.

---

## 2. Crush INP (2026, most important)

INP measures the delay "from a click/tap/key input until the screen next paints." The cause is almost always **JavaScript blocking the main thread.**

### 2-1. Don't send JS in the first place (the biggest lever)

React Server Components (RSC) don't send JS for processing that completes on the server to the client. **Confining `"use client"` to the smallest leaf** is the heart of INP improvement.

```tsx
// ❌ ページ全体をクライアント化：不要な JS が大量にハイドレーションされる
"use client";
export default function Page() { /* 重い */ }

// ✅ 静的部分は RSC のまま。対話する葉だけ client にする
export default function Page() {
  return (
    <>
      <ProductInfo />        {/* RSC：JS ゼロ */}
      <AddToCartButton />    {/* "use client" の小さな葉だけ */}
    </>
  );
}
```

### 2-2. Split long tasks and yield to the main thread

Mark heavy updates as "non-urgent" with `useTransition` / `useDeferredValue` to protect input responsiveness.

```tsx
"use client";
import { useState, useDeferredValue } from "react";

function Search({ items }: { items: Item[] }) {
  const [query, setQuery] = useState("");
  // 入力は即時反映、重いフィルタリングは「遅延」させて応答性を確保
  const deferred = useDeferredValue(query);
  const results = useMemo(() => filterItems(items, deferred), [items, deferred]);

  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} aria-label="検索" />
      <ResultList results={results} />
    </>
  );
}
```

Further effective moves:

- **Keep event handlers light.** Don't do heavy computation in a handler; if needed, split into chunks with `scheduler.yield()` (supported environments) or `setTimeout`.
- **Debounce / throttle.** Rate-limit continuous input, scroll, and resize.
- **Defer third-party JS.** Push analytics, chat, and ads back with `next/script`'s `strategy="lazyOnload"`, etc.
- **Keep the DOM small.** Virtualize (windowing) huge lists to suppress rendering cost.

> Note: in apps with real-time editing or frequent re-rendering (collaborative editing, score entry, etc.), INP directly becomes "snappiness." The granularity design of state updates sways the UX.

---

## 3. Get LCP under 2.5 seconds

LCP is the time until "the largest element (often a hero image or heading)" becomes visible. In order of effectiveness.

### 3-1. Images: `next/image` and `priority`

```tsx
import Image from "next/image";

// LCP になるヒーロー画像には priority を付け、プリロードさせる
<Image
  src="/hero.webp"
  alt="サービスのヒーロー画像"
  width={1200}
  height={630}
  priority            // ← LCP 画像に必須。遅延読み込みを無効化しプリロード
  sizes="100vw"
/>
```

`next/image` automatically does WebP/AVIF conversion, responsive generation, and lazy loading. **But attach `priority` only to the LCP image** (a frequent forgotten-to-attach bug). Conversely, don't attach `priority` to off-screen images.

### 3-2. Fonts: erase FOUT/FOIT with `next/font`

```tsx
import { Noto_Sans_JP } from "next/font/google";

const noto = Noto_Sans_JP({
  subsets: ["latin"],
  display: "swap",      // テキストを即表示（不可視待ちを避ける）
  preload: true,
});
```

`next/font` self-hosts the font at build time and erases external requests. With `display: "swap"`, it avoids "the time text is invisible (FOIT)" while suppressing shifts from glyph differences (CLS) with `size-adjust`-equivalent adjustments.

### 3-3. Server: TTFB and SSR/streaming

- **Static generation, caching.** As much as possible, front-load server processing with static generation (SSG) / ISR / PPR.
- **Streaming and `<Suspense>`.** Without waiting for heavy data, first stream the skeleton (and the LCP element).
- **`preconnect` / `dns-prefetch`.** Front-load connections to essential external origins (CDN, measurement).

---

## 4. Bring CLS near zero

CLS is "the amount elements jolt during loading." The cause concentrates in "**elements appearing later whose dimensions aren't reserved.**"

- **Explicit dimensions for images, video, iframes, and ads.** Always specify `width`/`height` (or `aspect-ratio`) to reserve the area first.
- **Countermeasure for font-swap shifts.** `next/font` + `display: swap` + metric adjustment.
- **Countermeasure for scrollbar shifts.** Prevent horizontal shift on appearance with `scrollbar-gutter: stable` (see the [Tailwind v4 guide](/blog/tailwind-css-v4-css-first-design-tokens-production-guide)).
- **Don't push down from above with dynamic insertion.** For banners/notifications, reserve the area or use an overlay that doesn't move the layout.
- **Show skeleton UI** at the same dimensions as the real content.

```css
/* 後から入る要素も含め、領域を先に確保する */
.thumb {
  aspect-ratio: 16 / 9; /* 画像読込前から高さが確定し、ズレない */
}
```

---

## 5. Measurement: stop guessing, improve with field numbers

### 5-1. Take field measurement (real users) yourself

Collect real-user values with the `web-vitals` library and send them to an analytics foundation. In Next.js, you can use `useReportWebVitals`.

```tsx
"use client";
import { useReportWebVitals } from "next/web-vitals";

export function WebVitals() {
  useReportWebVitals((metric) => {
    // 例：計測ビーコンで送信（sendBeacon は離脱時も送れる）
    const body = JSON.stringify({ name: metric.name, value: metric.value, id: metric.id });
    navigator.sendBeacon?.("/api/vitals", body);
  });
  return null;
}
```

- **The lab (Lighthouse / PageSpeed Insights)** is for reproduction and debugging. Build it into CI to detect regressions.
- **The field (CrUX / your own RUM)** is decisive for pass/fail. Look at the distribution of devices, networks, and regions and improve the 75th percentile.

### 5-2. Operate it as observability

- Record the 3 metrics per release so you **notice degradation** (resilience).
- Decompose metrics by route and by device to grasp where the bottleneck localizes.
- Dashboarding "speed" keeps improvement continuous. Optimization without measurement tends to be a YAGNI violation.

---

## 6. Next.js-specific effective points (cheat sheet)

| Problem | Means |
| ---- | ---- |
| Too much unnecessary JS (INP) | RSC-ify, confine `"use client"` to leaves, lazy-load with `next/dynamic` |
| Slow LCP image | `next/image` + `priority` + appropriate `sizes` |
| Delay/shift from fonts | `next/font` + `display: swap` |
| Slow server response | SSG / ISR / PPR, `<Suspense>` streaming, caching |
| Heavy third parties | Adjust `next/script`'s `strategy`, load only when needed |
| Layout shift | Dimension reservation, `aspect-ratio`, `scrollbar-gutter: stable` |

It's KISS to crush, in order, the bottlenecks identified by measurement — not "optimize everything for now."

---

## 7. Antipatterns

- ❌ **Satisfied with Lighthouse 100.** Pass/fail is the field (real-user 75%ile). Don't look only at the lab.
- ❌ **`"use client"` on the whole page.** The main cause of INP worsening. Limit it to interactive leaves only.
- ❌ **Forgetting `priority` on the LCP image** / attaching it to off-screen images.
- ❌ **Not specifying dimensions for image/ad slots.** CLS jumps.
- ❌ **Loading third-party JS synchronously in `<head>`.** Blocks the main thread.
- ❌ **Sacrificing accessibility for speed.** Even with virtualization or skeletons, maintain focus and reading order.

---

## 8. FAQ

**Q. What's the difference between INP and FID?**
A. FID measured only "the delay of the first operation," but INP measures the responsiveness of **all operations during the page stay.** In March 2024, INP officially replaced FID.

**Q. Do Core Web Vitals affect SEO ranking?**
A. They're one ranking factor. But it's not "good = always top"; content quality is the premise. It's a differentiator between comparable content, and works indirectly via bounce rate and CV too.

**Q. SPA or SSR, which is advantageous?**
A. Generally SSR / RSC is advantageous for LCP and INP. Less JS is sent to the client, and the initial render is faster.

**Q. Is `next/image` alone enough for image optimization?**
A. It covers most, but the effect comes only when you include the LCP image's `priority`, appropriate `sizes`, format (WebP/AVIF), and dimension reservation.

**Q. Where should I start?**
A. First measurement (field). Then the most failure-prone INP (JS reduction), followed by LCP (image, font, SSR), then CLS (dimension reservation) — that order is the most cost-effective.

---

## Conclusion: speed is "measurable quality" and affects revenue

Core Web Vitals quantify user experience. Improvement directly connects to improving search inflow, bounce rate, and conversion.

1. **Start from measurement (field).** Don't believe only the lab.
2. **For INP, reduce JS.** RSC-ifying and localizing `"use client"` is the heart.
3. **For LCP, images, fonts, SSR.** `priority` and `next/font` reliably.
4. **For CLS, dimension reservation.** Erase shifts with `aspect-ratio` and `scrollbar-gutter`.
5. **Operate it as observability.** Have a mechanism to notice degradation.

Perceived speed is trust in the product itself. A slow site isn't valued no matter how excellent its features.

**If you need to improve your site's display speed and Core Web Vitals, or to newly develop a fast, responsive app, feel free to consult me.** The case study below introduces the process of designing and implementing a responsiveness-focused real-time app that runs snappily even with multiple people operating simultaneously.
