Skip to main content

Next.js Core Web Vitals

Core Web Vitals are a set of specific metrics that Google uses to evaluate the user experience of your website. In this guide, we'll explore what Core Web Vitals are, why they matter for your Next.js application, and how to optimize them effectively.

What are Core Web Vitals?

Core Web Vitals are three specific measurements that Google uses to evaluate the user experience of a webpage:

  1. Largest Contentful Paint (LCP): Measures loading performance
  2. First Input Delay (FID): Measures interactivity
  3. Cumulative Layout Shift (CLS): Measures visual stability

These metrics are not just important for user experience but also directly impact your website's search engine rankings as part of Google's page experience signals.

Core Web Vitals Thresholds

MetricGoodNeeds ImprovementPoor
LCP≤ 2.5s2.5s - 4s> 4s
FID≤ 100ms100ms - 300ms> 300ms
CLS≤ 0.10.1 - 0.25> 0.25

Measuring Core Web Vitals in Next.js

Next.js has built-in support for measuring and reporting Web Vitals. Let's see how to set this up:

Using the Next.js Web Vitals API

Next.js provides a simple way to measure and report Core Web Vitals through the reportWebVitals function:

jsx
// pages/_app.js
export function reportWebVitals(metric) {
console.log(metric);
}

This function gives you access to various metrics, including Core Web Vitals. You can send these metrics to an analytics service:

jsx
// pages/_app.js
export function reportWebVitals(metric) {
const { id, name, label, value } = metric;

// Analytics
console.log(`Metric: ${name} | Value: ${value}`);

// Example: Send to Google Analytics
if (window.gtag) {
window.gtag('event', name, {
value: Math.round(name === 'CLS' ? value * 1000 : value),
event_category: 'Web Vitals',
event_label: id,
non_interaction: true,
});
}
}

Using Chrome DevTools

For local development, you can use Chrome DevTools to measure Core Web Vitals:

  1. Open Chrome DevTools (F12 or right-click > Inspect)
  2. Go to the "Lighthouse" tab
  3. Check "Performance" and "SEO" options
  4. Click "Generate report"

Optimizing Core Web Vitals in Next.js

Let's dive into how to optimize each Core Web Vital in your Next.js application.

Optimizing Largest Contentful Paint (LCP)

LCP measures how quickly the main content of your page loads. Here are some strategies to improve it:

1. Use Image Optimization

Next.js provides the Image component that automatically optimizes images:

jsx
import Image from 'next/image';

function HomePage() {
return (
<div>
<h1>Welcome to my website</h1>
<Image
src="/profile.jpg"
alt="Profile picture"
width={500}
height={300}
priority
/>
</div>
);
}

export default HomePage;

The priority attribute tells Next.js to preload this image since it's important for LCP.

2. Implement Server-Side Rendering or Static Generation

Next.js makes it easy to pre-render pages:

jsx
// pages/blog/[slug].js
export async function getStaticProps({ params }) {
const post = await getPostData(params.slug);
return {
props: {
post,
},
};
}

export async function getStaticPaths() {
const paths = getAllPostSlugs();
return {
paths,
fallback: false,
};
}

3. Minimize Render-Blocking Resources

Reduce the impact of CSS and JavaScript on loading times:

jsx
// pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
return (
<Html>
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
media="print"
onLoad="this.media='all'"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}

This example uses the media="print" trick to load fonts in a non-render-blocking way.

Optimizing First Input Delay (FID)

FID measures how quickly your site responds to user interactions.

1. Code-Splitting

Next.js automatically code-splits your application at the page level. You can further optimize with dynamic imports:

jsx
import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
loading: () => <p>Loading...</p>,
});

function HomePage() {
return (
<div>
<h1>Welcome</h1>
<HeavyComponent />
</div>
);
}

2. Minimize JavaScript Execution Time

Defer non-critical JavaScript and break up long tasks:

jsx
// Use the useEffect hook to run non-critical code after render
import { useEffect } from 'react';

function HomePage() {
useEffect(() => {
// Non-critical initialization code
const analyticsScript = document.createElement('script');
analyticsScript.src = 'https://analytics.example.com/script.js';
document.body.appendChild(analyticsScript);
}, []);

return <div>Hello World</div>;
}

Optimizing Cumulative Layout Shift (CLS)

CLS measures visual stability and unexpected layout shifts.

1. Always specify dimensions for images and videos

jsx
// Bad example - may cause layout shift
<img src="/profile.jpg" alt="Profile" />

// Good example - prevents layout shift
<Image
src="/profile.jpg"
alt="Profile"
width={640}
height={427}
/>

2. Reserve space for dynamic content

jsx
// styles/Home.module.css
.adContainer {
min-height: 250px;
width: 300px;
background-color: #f0f0f0;
}

// pages/index.js
import styles from '../styles/Home.module.css';

function HomePage() {
return (
<div>
<h1>Welcome</h1>
<div className={styles.adContainer}>
{/* Ad will load here */}
</div>
</div>
);
}

3. Avoid inserting content above existing content

jsx
// Bad practice
function BadNotification({ message }) {
return (
<>
<div className="notification">{message}</div>
<main>{/* Rest of the content */}</main>
</>
);
}

// Better practice
function GoodNotification({ message }) {
return (
<>
<div style={{ height: '40px' }}>
{message && <div className="notification">{message}</div>}
</div>
<main>{/* Rest of the content */}</main>
</>
);
}

Real-World Example: Optimizing an E-commerce Product Page

Let's apply what we've learned to optimize a product page:

jsx
// pages/product/[id].js
import { useEffect, useState } from 'react';
import Head from 'next/head';
import Image from 'next/image';
import dynamic from 'next/dynamic';

// Dynamically import heavy components
const ProductReviews = dynamic(() => import('../../components/ProductReviews'), {
loading: () => <div style={{ height: '200px' }}>Loading reviews...</div>,
});

// Server-side rendering for critical content
export async function getServerSideProps({ params }) {
const product = await fetchProductById(params.id);
return {
props: { product },
};
}

function ProductPage({ product }) {
const [relatedProducts, setRelatedProducts] = useState([]);

// Load non-critical data client-side
useEffect(() => {
async function loadRelatedProducts() {
const related = await fetchRelatedProducts(product.id);
setRelatedProducts(related);
}

loadRelatedProducts();
}, [product.id]);

return (
<div className="product-container">
<Head>
<title>{product.name} - Our Store</title>
<meta name="description" content={product.description.slice(0, 160)} />
</Head>

<div className="product-hero">
<div className="product-image">
<Image
src={product.image}
alt={product.name}
width={600}
height={400}
priority // Mark as LCP element
/>
</div>

<div className="product-info">
<h1>{product.name}</h1>
<p className="price">${product.price}</p>
<button className="add-to-cart">Add to Cart</button>
</div>
</div>

<div className="product-description">
<h2>Description</h2>
<p>{product.description}</p>
</div>

{/* Reserve space for reviews to prevent layout shift */}
<div style={{ minHeight: '200px' }}>
<ProductReviews productId={product.id} />
</div>

{/* Related products section */}
<div className="related-products">
<h2>Related Products</h2>
<div className="related-products-grid">
{relatedProducts.length ? (
relatedProducts.map(item => (
<div key={item.id} className="related-product-card">
{/* More content */}
</div>
))
) : (
// Placeholder with exact same height as loaded content
Array(4).fill(0).map((_, i) => (
<div key={i} className="related-product-placeholder"></div>
))
)}
</div>
</div>
</div>
);
}

export default ProductPage;

Monitoring Core Web Vitals in Production

Once you've implemented optimizations, it's important to monitor your Core Web Vitals in production:

1. Using Vercel Analytics

If you deploy with Vercel, you get Web Vitals analytics automatically:

jsx
// pages/_app.js
export function reportWebVitals(metric) {
// Vercel Analytics are automatically collected
}

2. Custom Analytics Integration

jsx
// pages/_app.js
export function reportWebVitals(metric) {
// Filter out non-web-vitals metrics
if (metric.name === 'FCP' ||
metric.name === 'LCP' ||
metric.name === 'CLS' ||
metric.name === 'FID' ||
metric.name === 'TTFB') {

// Construct measurement
const body = JSON.stringify({
metric_name: metric.name,
metric_value: metric.value,
metric_id: metric.id,
url: window.location.href,
});

// Send to analytics endpoint
navigator.sendBeacon('/api/metrics', body);
}
}

3. Using Google Search Console and PageSpeed Insights

Google Search Console provides Core Web Vitals reports for your site based on real user data. PageSpeed Insights gives more detailed analysis of specific pages.

Summary

Core Web Vitals are essential performance metrics that directly impact user experience and SEO. In Next.js, we can optimize them through:

  • For LCP: Image optimization, server-side rendering, and reducing render-blocking resources
  • For FID: Code splitting, minimizing JavaScript execution, and deferring non-critical work
  • For CLS: Setting dimensions for media, reserving space for dynamic content, and avoiding inserting content above existing content

By following these practices, you'll create faster, more responsive, and visually stable Next.js applications that provide better user experiences and rank better in search results.

Additional Resources

Exercises

  1. Analyze a Next.js webpage you've created using PageSpeed Insights and identify the Core Web Vitals that need improvement.
  2. Implement the reportWebVitals function in your Next.js application to track metrics to the console.
  3. Optimize the LCP of your homepage by ensuring images are properly sized and using the priority attribute.
  4. Identify and fix elements causing layout shifts (CLS) in your application.
  5. Create a dashboard using the Web Vitals API to monitor your application's performance over time.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)