"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.
// ❌ ページ全体をクライアント化:不要な 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.
"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) orsetTimeout. - Debounce / throttle. Rate-limit continuous input, scroll, and resize.
- Defer third-party JS. Push analytics, chat, and ads back with
next/script'sstrategy="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
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
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(oraspect-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). - 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.
/* 後から入る要素も含め、領域を先に確保する */
.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.
"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
priorityon 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.
- Start from measurement (field). Don't believe only the lab.
- For INP, reduce JS. RSC-ifying and localizing
"use client"is the heart. - For LCP, images, fonts, SSR.
priorityandnext/fontreliably. - For CLS, dimension reservation. Erase shifts with
aspect-ratioandscrollbar-gutter. - 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.