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:
- Largest Contentful Paint (LCP): Measures loading performance
- First Input Delay (FID): Measures interactivity
- 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
Metric | Good | Needs Improvement | Poor |
---|---|---|---|
LCP | ≤ 2.5s | 2.5s - 4s | > 4s |
FID | ≤ 100ms | 100ms - 300ms | > 300ms |
CLS | ≤ 0.1 | 0.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:
// 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:
// 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:
- Open Chrome DevTools (F12 or right-click > Inspect)
- Go to the "Lighthouse" tab
- Check "Performance" and "SEO" options
- 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:
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:
// 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:
// 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:
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:
// 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
// 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
// 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
// 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:
// 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:
// pages/_app.js
export function reportWebVitals(metric) {
// Vercel Analytics are automatically collected
}
2. Custom Analytics Integration
// 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
- Next.js Documentation on Web Vitals
- Web Vitals on web.dev
- Google PageSpeed Insights
- Vercel Analytics for Next.js
Exercises
- Analyze a Next.js webpage you've created using PageSpeed Insights and identify the Core Web Vitals that need improvement.
- Implement the
reportWebVitals
function in your Next.js application to track metrics to the console. - Optimize the LCP of your homepage by ensuring images are properly sized and using the
priority
attribute. - Identify and fix elements causing layout shifts (CLS) in your application.
- 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! :)