Skip to content
C.W.K.
Stream
Lesson 08 of 08 · published

Performance & Core Web Vitals

~22 min · LCP, CLS, INP, Web Vitals

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

The three metrics that matter

MetricTargetsNext.js levers
LCP — Largest Contentful Paint< 2.5sSSR, image preload, streaming, edge caching
CLS — Cumulative Layout Shift< 0.1next/image reserves space, next/font kills swap shift
INP — Interaction to Next Paint< 200msServer Components shrink JS, React Transitions yield

What you get for free

  • Server Components — less JS shipped → faster TTI / better INP.
  • Automatic per-route code splitting — no extra config.
  • next/image serves WebP/AVIF + responsive sizes.
  • next/font self-hosts fonts, eliminates swap CLS.
  • <Link> prefetches by default.
  • Streaming + Suspense for perceived performance.

Measuring

Run Lighthouse in CI. Add Vercel's @vercel/speed-insights or your own Web Vitals reporter for real-user metrics. Synthetic Lighthouse is a sanity check; real users are the truth.

Optimize the measured limiting experience: LCP needs its critical resource and fast initial response, CLS needs reserved geometry, and INP needs a responsive main thread. Measure real routes and devices before trading server work, client JavaScript, caching, or image quality. A perfect local Lighthouse score is not production performance. Warm caches, fast hardware, no authenticated data, and an idle extension-free browser omit the conditions users face. Framework defaults are a baseline, not proof. Capture cold and warm lab traces for representative mobile and desktop routes, then compare them with field percentiles. Attribute the LCP element, each layout shift, and the longest interaction task, make one change, and verify the intended metric moved without regressing another.

Code

Bundle analyzer in next.config·ts
// next.config.ts
import type { NextConfig } from 'next';
import withBundleAnalyzer from '@next/bundle-analyzer';

const withAnalyzer = withBundleAnalyzer({ enabled: process.env.ANALYZE === 'true' });

const config: NextConfig = {
  images: { remotePatterns: [{ protocol: 'https', hostname: 'images.example.com' }] },
};

export default withAnalyzer(config);
Bash to view the report·bash
ANALYZE=true npm run build
# opens .next/analyze/client.html with treemap of every client chunk
Real-user Vitals reporter·tsx
// app/layout.tsx
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html><body>
      {children}
      <Analytics />
      <SpeedInsights />
    </body></html>
  );
}

External links

Exercise

Run the bundle analyzer on your project. Identify the three biggest client chunks. For each, decide whether you can move work into a Server Component or lazy-load the chunk. Apply at least one fix.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.