Skip to main content

Next.js Performance Overview

Introduction

Web performance has a significant impact on user experience and business metrics. Research consistently shows that faster websites improve conversion rates, reduce bounce rates, and increase user engagement. In the competitive digital landscape, every millisecond counts.

Next.js is a React framework designed with performance in mind, providing developers with built-in optimizations and tools to create fast web applications. This guide will introduce you to the core concepts of performance in Next.js and provide an overview of the optimization techniques available.

Why Performance Matters

Before diving into Next.js-specific optimizations, let's understand why web performance is critical:

  • User Experience: Fast-loading sites create positive user experiences
  • SEO Rankings: Google uses page speed as a ranking factor
  • Conversion Rates: Faster sites have higher conversion rates
  • Accessibility: Performance optimization makes sites more accessible to users with slower connections
  • Cost Efficiency: Optimized applications require fewer server resources

Next.js Performance Foundations

Next.js provides several built-in performance optimizations that work out of the box:

1. Automatic Code Splitting

Next.js automatically splits your JavaScript code by route, ensuring users only download the code needed for the current page.

jsx
// pages/index.js
// This code will only be loaded when users visit the homepage
export default function Home() {
return <h1>Welcome to the homepage!</h1>
}

// pages/about.js
// This code will only be loaded when users visit the about page
export default function About() {
return <h1>About our company</h1>
}

This approach significantly reduces the initial bundle size, improving loading times.

2. Server-Side Rendering (SSR)

Next.js allows components to render on the server before sending HTML to the client:

jsx
// pages/products.js
export async function getServerSideProps() {
// This code runs on the server
const res = await fetch('https://api.example.com/products')
const products = await res.json()

return {
props: {
products, // Will be passed to the page component as props
},
}
}

export default function Products({ products }) {
return (
<div>
<h1>Products</h1>
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
)
}

Benefits of SSR:

  • Faster First Contentful Paint (FCP)
  • Improved SEO as search engines see fully rendered content
  • Better user experience on initial load

3. Static Site Generation (SSG)

For content that doesn't change frequently, Next.js can pre-render pages at build time:

jsx
// pages/blog/[slug].js
export async function getStaticPaths() {
// Get all blog post slugs
const posts = await getBlogPosts()

return {
paths: posts.map(post => ({ params: { slug: post.slug } })),
fallback: false // Show 404 for non-existent slugs
}
}

export async function getStaticProps({ params }) {
// Get content for a specific blog post
const post = await getBlogPostBySlug(params.slug)

return {
props: { post },
// Re-generate the page every 10 minutes if requested
revalidate: 600,
}
}

export default function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}

SSG offers the fastest possible performance since pages are pre-rendered at build time, allowing for global CDN distribution.

4. Image Optimization

Next.js includes an Image component that automatically optimizes images:

jsx
import Image from 'next/image'

export default function ProductPage() {
return (
<div>
<h1>Product Name</h1>
<Image
src="/images/product.jpg"
alt="Product image"
width={500}
height={300}
priority
/>
</div>
)
}

The Image component:

  • Automatically serves images in modern formats like WebP
  • Resizes images to prevent sending unnecessary pixels
  • Lazy loads images by default (images load as they enter the viewport)
  • Prevents layout shift through required width/height properties

5. Font Optimization

Next.js 10+ includes automatic font optimization:

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

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

Next.js will:

  • Inline the font CSS at build time
  • Host font files locally when possible
  • Eliminate render-blocking requests

Performance Measurement in Next.js

To optimize effectively, you must first measure performance. Next.js integrates with several tools for this purpose:

Built-in Analytics

Next.js provides built-in performance analytics through its Vercel integration:

jsx
// next.config.js
module.exports = {
analyticsId: 'your-analytics-id',
}

This tracks Core Web Vitals metrics automatically when deployed to Vercel.

Web Vitals API

You can collect performance metrics using the Web Vitals JavaScript library:

jsx
// pages/_app.js
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import * as gtag from '../lib/gtag'
import { getCLS, getFID, getLCP } from 'web-vitals'

function reportWebVitals({ id, name, label, value }) {
// Send metrics to your analytics service
gtag.event({
action: name,
category: label,
label: id,
value: Math.round(name === 'CLS' ? value * 1000 : value),
})
}

function MyApp({ Component, pageProps }) {
const router = useRouter()

useEffect(() => {
// Report core web vitals
getCLS(reportWebVitals)
getFID(reportWebVitals)
getLCP(reportWebVitals)
}, [])

return <Component {...pageProps} />
}

export default MyApp

Real-World Optimization Example

Let's examine a real-world example of optimizing a product listing page:

Before Optimization

jsx
// pages/products.js - Initial Version
import { useState, useEffect } from 'react'
import Head from 'next/head'

export default function Products() {
const [products, setProducts] = useState([])
const [loading, setLoading] = useState(true)

useEffect(() => {
async function fetchProducts() {
const res = await fetch('/api/products')
const data = await res.json()
setProducts(data)
setLoading(false)
}

fetchProducts()
}, [])

if (loading) return <p>Loading products...</p>

return (
<div>
<Head>
<title>Our Products</title>
</Head>

<h1>Products</h1>
<div className="product-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<h2>{product.name}</h2>
<p>${product.price}</p>
</div>
))}
</div>
</div>
)
}

Issues with this implementation:

  • Client-side data fetching causes delayed content loading
  • No image optimization
  • Layout shift when images load
  • Every user performs the same data fetch

After Optimization

jsx
// pages/products.js - Optimized Version
import Image from 'next/image'
import Head from 'next/head'

export async function getStaticProps() {
// Fetch at build time instead of client-side
const res = await fetch('https://api.example.com/products')
const products = await res.json()

return {
props: {
products,
},
// Re-generate page every 10 minutes
revalidate: 600,
}
}

export default function Products({ products }) {
return (
<div>
<Head>
<title>Our Products</title>
<meta name="description" content="Browse our product catalog" />
</Head>

<h1>Products</h1>
<div className="product-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<div className="image-container">
<Image
src={product.image}
alt={product.name}
width={300}
height={200}
loading="lazy"
/>
</div>
<h2>{product.name}</h2>
<p>${product.price}</p>
</div>
))}
</div>
</div>
)
}

Improvements:

  • Pre-rendered content with Incremental Static Regeneration
  • Optimized images with automatic format conversion and resizing
  • No layout shift with image dimensions defined
  • Improved SEO with proper metadata
  • Fast Time to First Byte (TTFB) and First Contentful Paint (FCP)

Common Performance Bottlenecks and Solutions

BottleneckSolution
Large JavaScript bundlesCode splitting, tree shaking, dynamic imports
Slow server responseEdge functions, caching, optimized backend
Unoptimized imagesUse Next.js Image component
Render-blocking resourcesNext/script for proper script loading
Unnecessary re-rendersReact.memo, useMemo, useCallback

Summary

Next.js provides powerful built-in performance optimizations that help developers create fast, efficient web applications with minimal configuration:

  • Code Splitting: Automatic route-based code splitting
  • Rendering Options: Server-side rendering and static generation
  • Image Optimization: Automatic image resizing and format conversion
  • Font Optimization: Improved font loading and display
  • Analytics: Built-in performance tracking

By understanding and leveraging these features, you can create web applications that load quickly and provide a superior user experience, even as your project scales.

Additional Resources

Exercises

  1. Convert an existing client-side rendered page in your application to use SSG or ISR
  2. Replace standard <img> tags with the Next.js Image component
  3. Set up Web Vitals reporting to track Core Web Vitals in your application
  4. Use the Network tab in Chrome DevTools to identify and optimize the largest assets on your page
  5. Implement dynamic imports for a heavy component that isn't needed on initial page load


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