Home / guides / Next.js
Core Web Vitals for Next.js
Updated · by RankVitals
Next.js gives you the best performance primitives of any React framework — server components, streaming, next/image, next/font — and then lets you opt out of all of them one “use client” at a time. Core Web Vitals on Next.js are won or lost in the client bundle, the LCP image, and your caching strategy. This guide covers the specific APIs and build-time signals that decide each metric.
Real-world measurements, July 2026
Mobile lab results for well-known Next.js sites, measured with Lighthouse — the same engine behind Google PageSpeed Insights and RankVitals tests. Snapshot taken July 19, 2026 — your numbers will differ, which is the point: measure your own site.
| Site | Perf score | LCP (lab) | TBT | CLS |
|---|---|---|---|---|
| nextjs.org | 87/100 | 2.2 s | 436 ms | 0 |
| vercel.com | 41/100 | 5.0 s | 3106 ms | 0 |
Lab data: Lighthouse 13, emulated Moto G Power over simulated 4G — the identical methodology PageSpeed Insights uses for its lab section. Single runs; lab numbers vary a few percent between runs.
What real Next.js sites measure
Framework-maker sites make a clean A/B test, and we measured both with Lighthouse 13 (mobile emulation) in July 2026. nextjs.org scored 87/100 — LCP 2.2 s, inside the 2.5 s threshold, TBT 436 ms, CLS 0 — a content-focused Next.js site doing what the framework promises. vercel.com scored 41/100: LCP 5.0 s and TBT 3,106 ms, the cost of a marketing site dense with animated demos, third-party scripts, and client-side interactivity — on the same framework, from the same company. CLS was zero on both, and that is representative: layout stability is close to a solved problem in Next.js, because next/image and next/font reserve space by design. The two numbers bracket the Next.js reality — the framework gives you an 87; your client bundle decides how much of it you keep.
Ship less JavaScript: server components and the client boundary
With the App Router, every component is a React Server Component until you say otherwise — its code never ships to the browser. Total Blocking Time regressions come from where you draw the “use client” boundary: mark a layout or page client-side and everything it imports joins the bundle. Push “use client” to the leaves — the button, the search box, the carousel — and keep data fetching and markup on the server.
Make the cost visible. next build prints first-load JS per route: treat anything over ~150KB as a budget breach and investigate with @next/bundle-analyzer. The usual suspects are heavyweight dependencies imported into shared layouts (date libraries, chart kits, icon barrels — mitigate icon and utility barrels with optimizePackageImports in next.config). For genuinely heavy below-the-fold widgets — editors, maps, charts — use next/dynamic so they load after the critical path, with ssr: false when they are client-only.
next/image, priority, and the LCP
next/image solves format (AVIF/WebP via the formats config), sizing (srcset generation), and lazy loading — but it lazy-loads everything by default, including your hero. The single most impactful line on many Next.js pages is adding priority to the LCP image: it disables lazy loading and emits a preload so the fetch starts immediately. Audit every above-the-fold image for it, and only the above-the-fold ones — priority on offscreen images wastes bandwidth the hero needs.
The second-most impactful attribute is sizes. Without it, the browser assumes the image spans the full viewport and picks a needlessly large srcset candidate. A hero that renders at 100vw on mobile but 50vw on desktop should say so: sizes="(max-width: 768px) 100vw, 50vw". For remote images, configure remotePatterns so the optimizer can process them instead of passing originals through untouched.
Fonts, hydration, and layout shift
CLS on Next.js sites rarely comes from images (next/image reserves space via width/height) — it comes from fonts and post-hydration content. next/font eliminates the font half: it self-hosts Google or local fonts, removes the third-party request, and applies size-adjust metrics to the fallback font so the swap doesn’t reflow the page. If you still load fonts via a <link> to Google Fonts, migrating to next/font is a mechanical, high-yield change.
The other half is content that appears after hydration: client components that render nothing on the server (the classic useEffect-gated auth or theme check), ad slots, and data that pops in. Reserve the space — explicit dimensions or CSS aspect-ratio on containers — or stream the real content from the server inside a Suspense boundary with a correctly sized fallback, so the layout is stable from first paint.
Caching and TTFB: fetch, ISR, and PPR
A Server Component that awaits an uncached fetch puts your API’s latency inside your TTFB — every visitor pays it. Decide per data source how fresh is fresh enough: fetch with next: { revalidate: 3600 } (or route-level ISR via export const revalidate) serves cached HTML instantly and refreshes in the background. Content that changes on publish rather than on a clock should use on-demand revalidation with revalidateTag from your CMS webhook.
For pages that mix static shell with per-user content, Partial Prerendering (PPR) serves the static shell from the edge immediately and streams the dynamic holes — you keep a fast LCP without giving up personalization. And check where your functions run: data fetched from a single-region backend is fastest with compute in that same region; moving rendering to the edge while the database stays in one region often makes TTFB worse, not better.
Measuring Next.js correctly
Never judge performance on next dev — development builds skip minification, disable optimizations, and hydrate with extra instrumentation; they are routinely 5-10x slower than production. Measure next build + next start locally, or better, the deployed production URL, since platform-level compression, HTTP/2+, and CDN behavior are part of the result.
Preview deployments carry a footgun worth knowing: hosting platforms typically serve them with an X-Robots-Tag: noindex header (correctly — you don’t want previews indexed). If you copy headers or middleware from preview configurations to production, or test SEO on a preview URL, you will see noindex findings that don’t apply to production — and conversely, a misconfigured production deployment that keeps noindex is a catastrophic, silent SEO failure. Audit the production domain explicitly.
What our audit checks on Next.js sites
These are real checks from the RankVitals SEO audit that most often fire on Next.js sites. Run the audit to see which ones apply to yours.
noindexA preview-environment X-Robots-Tag or metadata robots setting leaking into production silently deindexes a Next.js site — this check catches it on the live domain.canonical_missingThe App Router does not emit canonicals unless you set alternates.canonical in the Metadata API — one of the most commonly skipped fields.structured_data_missingNext.js has no built-in JSON-LD helper, so structured data tends to be forgotten entirely; a small script-tag component per template fixes it.open_graphopenGraph fields in the Metadata API (and opengraph-image files) are easy wins that many Next.js projects leave unset.sitemap_missingApp Router sitemaps require an explicit sitemap.ts route — nothing is generated automatically, so new projects often ship without one.
Frequently asked questions
Why is my Next.js Lighthouse score bad in development?
next dev serves unminified code, disables production optimizations, and includes hot-reload and React development instrumentation. It is not representative and never will be. Run next build and next start, or measure the deployed production URL — scores typically jump dramatically between dev and production builds.
Does the App Router make my site faster than the Pages Router?
It can — Server Components keep component code off the client, and streaming plus Partial Prerendering improve time-to-content. But the gains only materialize if you keep the client boundary small. An App Router app with “use client” at the layout level ships as much JavaScript as the Pages Router ever did.
Should I deploy to the edge for better Core Web Vitals?
Only if your data is edge-available too. Edge rendering cuts network latency to the user, but if every request then fetches from a database in one region, you have added a hop, not removed one. Static + ISR pages served from a CDN edge give most sites the same latency win with far less complexity.
What first-load JS size should I aim for?
The next build output shows first-load JS per route. Under ~100KB is excellent, 100-150KB is normal for interactive apps, and beyond ~150KB you should be able to name the feature that justifies it. The number compounds: shared layout bundles load on every route, so trim those first.
Measure your Next.js site now
Free speed test with waterfall + filmstrip, and an SEO audit in the same tool.