Vercel Logo

Your Lighthouse score is 98 but Google Search Console shows "Poor" Core Web Vitals. Your site dropped in rankings and you don't know why.

Lighthouse measures lab data from synthetic tests. Google ranks pages using field data collected from users over 28 days. A score of 98 cannot explain why someone on a 3G connection sees a four-second LCP, so we need both kinds of measurement.

Outcome

Vitals instrumentation with Vercel Speed Insights or custom reporting. Track LCP < 2.5s, INP < 200ms, CLS < 0.1 targets. Real user data (not synthetic Lighthouse scores) informs performance improvements.

Fast Track

  1. Add @vercel/speed-insights for real-time Core Web Vitals tracking.
  2. Monitor LCP (loading), INP (interactivity), CLS (visual stability) in production.
  3. Use field data (real user measurements) to identify bottlenecks - Google only ranks based on real user data, not lab data (synthetic measurements from testing tools like Lighthouse).

Hands-On Exercise 3.6

Requirements:

  1. Install @vercel/speed-insights and @vercel/analytics for real-time vitals tracking.
  2. Capture LCP (Largest Contentful Paint), INP (Interaction to Next Paint), CLS (Cumulative Layout Shift).
  3. Set up custom reporting endpoint to log vitals to your analytics system.
  4. Document thresholds: LCP < 2.5s, INP < 200ms, CLS < 0.1.
  5. Track improvements after image/font/rendering optimizations.

Implementation hints:

  • Google uses field data only for ranking - real user data from Chrome over 28-day window.
  • Field data = 75th percentile of users over last 28 days.
  • Lighthouse scores don't affect SEO - only real user Core Web Vitals do.
  • Vercel Speed Insights shows real-time data - Google takes 28 days to reflect changes.
  • Core Web Vitals expose performance problems that users can feel.
  • Mobile and desktop tracked separately.
  • Only "sufficiently popular" and "publicly discoverable" pages tracked by Google.

Try It

  1. Monitor real-time vitals:

    • Deploy to Vercel with Speed Insights enabled.
    • Visit production site, interact with page.
    • Check Vercel dashboard for LCP, INP, CLS metrics.
  2. Test vitals locally:

    • Open DevTools Console, look for web-vitals logs.
    • Use Lighthouse Performance tab for lab data (not used for ranking).
    • Compare field data (real users) vs lab data (synthetic).
  3. Verify tracking:

    • Trigger interaction (click, type) and verify INP captured.
    • Scroll page and verify CLS remains < 0.1.
    • Check LCP element in Performance tab - should be hero image or main content.
Need Help Diagnosing Web Vitals Issues?

Seeing poor Core Web Vitals scores but unsure what's causing them? Use this prompt to debug:

Prompt: Diagnose Core Web Vitals Issues
<context>
I'm measuring Core Web Vitals for my Next.js app and need to identify and fix performance issues.
I have access to: [Lighthouse reports, Vercel Speed Insights, Chrome DevTools, PageSpeed Insights]
My current scores: [paste your LCP, INP, CLS values]
</context>
 
<current-metrics>
Report your Core Web Vitals measurements:
- **LCP (Largest Contentful Paint):** [value] ms - Target: < 2500ms
- **INP (Interaction to Next Paint):** [value] ms - Target: < 200ms
- **CLS (Cumulative Layout Shift):** [value] - Target: < 0.1
- **Device tested:** [Desktop, Mobile, or both]
- **Measurement source:** [Lighthouse, PageSpeed Insights, Speed Insights, Chrome DevTools]
</current-metrics>
 
<lcp-analysis>
If LCP is poor (> 2500ms):
- What is the LCP element? [Hero image, text block, video, etc.]
- Image size and format: [e.g., 2MB JPEG, 500KB PNG]
- Image loading: [Does the LCP image use next/image with the preload prop?]
- Server response time (TTFB): [Check Network tab]
- Blocking resources: [Large JavaScript bundles, render-blocking CSS]
</lcp-analysis>
 
<inp-analysis>
If INP is poor (> 200ms):
- What interaction is slow? [Button click, form input, navigation]
- JavaScript bundle size: [Check bundle analyzer or Network tab]
- Third-party scripts: [Analytics, chat widgets, ad scripts]
- Long tasks: [Check Performance tab for tasks > 50ms]
- Client-side rendering: [Heavy React re-renders, large state updates]
</inp-analysis>
 
<cls-analysis>
If CLS is poor (> 0.1):
- What elements shift? [Images, ads, fonts, injected content]
- Images without dimensions: [Missing width/height on Image component]
- Font loading: [FOIT/FOUT causing text reflow]
- Dynamic content: [Banners, popups injected after page load]
- Layout reserved space: [Skeleton screens, placeholders]
</cls-analysis>
 
<questions>
1. **Root cause:** Which specific element or resource is causing the poor metric?
2. **Quick wins:** What's the highest-impact fix I can implement first?
3. **Next.js optimizations:** Am I using the right rendering strategy, next/image, next/font?
4. **Trade-offs:** Will fixing this metric negatively impact another metric?
5. **Measurement gap:** Why do lab scores (Lighthouse) differ from field data (real users)?
</questions>
 
<specific-scenario>
Example issue:
- LCP: 4200ms (red - very poor)
- LCP element: Hero image (1.8MB JPEG, 2400x1600)
- Current implementation: <img src="/hero.jpg" />
- No preload for the LCP image, no format optimization
 
Diagnosis:
1. **Problem:** Large unoptimized image blocking LCP
2. **Solution:** Replace with `<Image preload>` for preloading and automatic WebP/AVIF formats
3. **Expected improvement:** LCP reduces to < 2000ms with optimized formats and preloading
</specific-scenario>
 
Diagnose my Core Web Vitals issues and provide specific Next.js optimizations with implementation code (next/image preload, Suspense boundaries, font display strategies, width/height attributes) and expected metric improvements for each fix.

This helps you systematically identify bottlenecks and apply the right Next.js optimizations to move metrics from red to green!

Commit & Deploy

git add -A
git commit -m "feat(advanced): add core web vitals measurement"
git push -u origin feat/advanced-vitals

Done-When

  • instrumentation-client.ts exists in apps/web/src/ and imports registerWebVitals
  • Open DevTools Console on any page: see "LCP:", "INP:", and "CLS:" log entries with numeric values
  • LCP value logged is < 2500ms (green zone) or identifies specific improvement needed
  • INP value logged is < 200ms after clicking a button or typing in an input
  • CLS value logged is < 0.1 after page fully loads and stabilizes
  • Open Vercel dashboard → Speed Insights: see real user Core Web Vitals data appearing
  • Run Lighthouse in DevTools → Performance tab: LCP element identified in "Largest Contentful Paint element" section

Solution

Click to reveal solution
apps/web/src/app/layout.tsx
import { SpeedInsights } from '@vercel/speed-insights/next'
import { Analytics } from '@vercel/analytics/react'
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  )
}
apps/web/src/lib/web-vitals.ts
import { onCLS, onINP, onLCP } from 'web-vitals'
 
export function registerWebVitals() {
  onLCP((metric) => {
    console.log('LCP:', metric.value, 'ms', 'Target: <2500ms')
    sendToAnalytics(metric)
  })
 
  onINP((metric) => {
    console.log('INP:', metric.value, 'ms', 'Target: <200ms')
    sendToAnalytics(metric)
  })
 
  onCLS((metric) => {
    console.log('CLS:', metric.value, 'Target: <0.1')
    sendToAnalytics(metric)
  })
}
 
function sendToAnalytics(metric: any) {
  const { id, name, value, rating } = metric
 
  // Send to your analytics endpoint
  fetch('/api/analytics/vitals', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      id,
      name,
      value,
      rating,
      url: window.location.pathname,
      timestamp: Date.now(),
    }),
  }).catch((error) => {
    console.error('Failed to send vitals:', error)
  })
}
apps/web/src/instrumentation-client.ts
import { registerWebVitals } from './lib/web-vitals'
 
// Initialize web vitals tracking before React hydration
// This ensures we capture metrics from the earliest possible moment
registerWebVitals()
Why instrumentation-client.ts?

The instrumentation-client.ts file runs before React hydration begins, making it the ideal place to initialize performance monitoring. This ensures you capture metrics from the earliest possible moment in the page lifecycle.

https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation-client

apps/web/src/app/api/analytics/vitals/route.ts
import { NextResponse } from 'next/server'
 
export async function POST(request: Request) {
  try {
    const vitals = await request.json()
 
    // Log to your analytics system (e.g., database, external service)
    console.log('Core Web Vitals:', vitals)
 
    // Store in database or forward to analytics service
    // await db.vitals.create({ data: vitals })
 
    return NextResponse.json({ success: true })
  } catch (error) {
    return NextResponse.json({ error: 'Failed to record vitals' }, { status: 500 })
  }
}
Install dependencies
pnpm add @vercel/speed-insights @vercel/analytics web-vitals
Working Directory

Run the installation command from the root of the monorepo, not inside apps/web. The package manager will handle workspace dependency installation.

Troubleshooting: No Console Logs Appearing?

If you don't see LCP, INP, or CLS logs in the console:

  1. Check file location: instrumentation-client.ts must be in apps/web/src/ (same level as app/ folder), not inside app/.
  2. Check import path: The import should be ./lib/web-vitals (relative to src/).
  3. Restart dev server: Fast Refresh does not pick up changes to instrumentation-client.ts, so stop and restart the server.
  4. Check for errors: Open DevTools Console and look for import errors or syntax issues.
  5. Wait for interaction: INP only logs after you interact with the page (click, type). LCP logs after the largest content element renders.
  6. Production vs dev: Some metrics behave differently in development. Deploy to Vercel for accurate field data.
Core Web Vitals Explained

LCP (Largest Contentful Paint): How quickly main content loads. Target: < 2.5s. Optimize with the image preload prop, responsive sizing, and a CDN.

INP (Interaction to Next Paint): How quickly page responds to interactions. Target: < 200ms. Optimize with code splitting, reduce JavaScript execution.

CLS (Cumulative Layout Shift): Visual stability during loading. Target: < 0.1. Optimize with image/font space reservation, avoid injected content.

Google Uses Field Data Only

Critical: Google search ranking uses field data (real user measurements from Chrome) over 28-day window. Your Lighthouse lab score doesn't affect SEO ranking. Focus on real user Core Web Vitals, not synthetic scores.

Field data collection:

  • Real users with Chrome browser (desktop and Android)
  • 75th percentile over last 28 days
  • Only "sufficiently popular" and "publicly discoverable" pages
  • Mobile and desktop tracked separately

Why it matters: Improvements take up to 28 days to reflect in Google ranking. Use Vercel Speed Insights for real-time feedback.

Core Web Vitals as UX Debugging Tool

Core Web Vitals describe user experience. Use each metric to choose what to debug:

  • LCP > 2.5s? Check image optimization, server response time.
  • INP > 200ms? Reduce JavaScript execution, defer non-critical scripts.
  • CLS > 0.1? Add image dimensions, font display strategies, avoid layout-shifting content.
Next.js 16.3 Turbopack Improvements

Turbopack is the default bundler in Next.js 16.3. The release adds two important caching improvements with no application-code changes:

Development Performance:

  • Memory eviction: Long next dev sessions use up to 90% less memory in the projects measured by the Next.js team
  • Disk caching: Dev compiler artifacts persist across restarts and are enabled by default
  • Incremental compilation: Only changed work is rebuilt, preserving fast feedback loops

Production Build Impact:

  • Persistent build cache: Repeat next build runs reuse unchanged artifacts; the Next.js team measured up to 5.5× faster builds on some projects
  • Faster server rendering: Native Node.js streams let App Router servers handle up to 22% more requests under load
  • Optional TypeScript 7: Install typescript@^7 to let next build use the faster native TypeScript CLI

Read the Next.js 16.3 release notes and Turbopack reference.

Important: Turbopack improves compilation and HMR. Runtime Core Web Vitals measure what happens after the page loads, so they require separate work on images, fonts, scripts, and rendering.

Indirect benefits: Faster builds and smaller bundles from better tree-shaking can reduce JavaScript parse time, but CWV improvements come primarily from:

  • Image optimization (next/image with preload for the page's LCP image)
  • Font optimization (next/font)
  • Code splitting and lazy loading
  • Server Components and streaming
  • Proper width/height attributes to prevent CLS

Development velocity: Faster builds mean more time optimizing performance, less time waiting for compilation.

References

Was this helpful?

supported.