Next.js Lazy Loading
Introduction
Lazy loading is a performance optimization technique that delays loading non-critical resources until they're actually needed. In the context of Next.js applications, lazy loading allows you to split your code into smaller chunks and load components only when they're required. This approach can significantly improve the initial loading time of your application, reduce bandwidth usage, and enhance the overall user experience.
Next.js provides several built-in mechanisms for implementing lazy loading:
- Component-level lazy loading with
dynamic
imports - Image lazy loading with the
next/image
component - Font optimization with
next/font
- Route-based code splitting (automatic in Next.js)
In this guide, we'll explore each of these approaches and learn how to implement them effectively in your Next.js applications.
Component-Level Lazy Loading
Using dynamic imports
Next.js provides the dynamic
function from the next/dynamic
module that allows you to dynamically import components. This is equivalent to using React's lazy
and Suspense
features but is specifically optimized for Next.js.
Basic Usage
import dynamic from 'next/dynamic';
// Instead of a regular import like:
// import HeavyComponent from '../components/HeavyComponent';
// Use dynamic import:
const HeavyComponent = dynamic(() => import('../components/HeavyComponent'));
function HomePage() {
return (
<div>
<h1>Welcome to my website</h1>
<HeavyComponent />
</div>
);
}
export default HomePage;
When a user visits this page, the main content will load first, and the HeavyComponent
will be loaded separately only when needed.
Adding a Loading State
You can show a loading indicator while the component is being loaded:
const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
loading: () => <p>Loading component...</p>,
});
Disabling Server-Side Rendering
For components that rely on browser-specific APIs, you might want to disable server-side rendering:
const ClientOnlyComponent = dynamic(() => import('../components/ClientOnlyComponent'), {
ssr: false,
loading: () => <p>Loading component...</p>,
});
Image Lazy Loading
Next.js provides the next/image
component which automatically implements lazy loading for images. The component only loads images when they enter the viewport, greatly improving page performance.
Basic Usage
import Image from 'next/image';
function ProductPage() {
return (
<div>
<h1>Featured Product</h1>
<Image
src="/images/product.jpg"
alt="Product image"
width={500}
height={300}
priority={false} // Set to true for above-the-fold images
/>
</div>
);
}
The Image
component automatically:
- Lazy loads images that are not in the viewport
- Optimizes image size based on the device
- Prevents layout shifts by reserving the space with the specified dimensions
Priority Images
For key images that are visible immediately when the page loads (above the fold), you should set the priority
attribute to true
to preload them:
<Image
src="/images/hero-image.jpg"
alt="Hero image"
width={1200}
height={600}
priority={true}
/>
Placeholder Images
You can add a blur placeholder while the image loads:
<Image
src="/images/product.jpg"
alt="Product image"
width={500}
height={300}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWEREiMxUf/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HautoooTnWycq//2Q=="
/>
Font Optimization with next/font
Next.js 13 introduced a new font system that automatically optimizes fonts by downloading them at build time and serving them with your application. This eliminates layout shifts caused by font loading.
Using Google Fonts
import { Inter } from 'next/font/google';
// Initialize the font object
const inter = Inter({
subsets: ['latin'],
display: 'swap',
});
function MyApp({ Component, pageProps }) {
return (
<main className={inter.className}>
<Component {...pageProps} />
</main>
);
}
Using Local Fonts
import localFont from 'next/font/local';
// Load a local font file
const myFont = localFont({
src: './fonts/my-font.woff2',
display: 'swap',
});
function MyComponent() {
return (
<div className={myFont.className}>
This text will use the custom font.
</div>
);
}
Route-Based Code Splitting
Next.js automatically code-splits your application at the page level. This means when a user navigates to a specific page, only the code for that page is loaded.
For example, if you have these files:
pages/index.js
pages/about.js
pages/contact.js
Next.js will create separate chunks for each page, and they'll only be loaded when a user navigates to that specific route.
Prefetching Routes
Next.js automatically prefetches linked pages that appear in the viewport. This means that by the time the user clicks a link, the destination page may already be loaded in the background:
import Link from 'next/link';
function Navbar() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/contact">Contact</Link>
</nav>
);
}
When these links are visible in the viewport, Next.js will automatically prefetch the linked pages in the background.
Real-World Example: E-commerce Product List
Let's build a practical example of a product list page that uses lazy loading techniques for better performance:
import { useState } from 'react';
import Image from 'next/image';
import dynamic from 'next/dynamic';
// Lazy load the product details modal
const ProductDetailModal = dynamic(() => import('../components/ProductDetailModal'), {
loading: () => <div className="modal-placeholder">Loading details...</div>,
ssr: false, // Since modals are client-side only
});
export default function ProductListPage({ products }) {
const [selectedProduct, setSelectedProduct] = useState(null);
return (
<div className="product-grid">
{products.map((product) => (
<div key={product.id} className="product-card">
<Image
src={product.imageUrl}
alt={product.name}
width={300}
height={200}
// Only prioritize first few images that would be above the fold
priority={product.id <= 4}
/>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => setSelectedProduct(product)}>
View Details
</button>
</div>
))}
{/* Modal only gets loaded when a product is selected */}
{selectedProduct && (
<ProductDetailModal
product={selectedProduct}
onClose={() => setSelectedProduct(null)}
/>
)}
</div>
);
}
// This function would fetch your products from an API or database
export async function getStaticProps() {
// Fetch products from your API
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return {
props: {
products,
},
revalidate: 60, // Re-generate page every 60 seconds
};
}
In this example:
- The product grid loads with optimized images
- Only the first few "above the fold" images are prioritized
- The product detail modal is lazy loaded only when needed
- The page is statically generated with incremental static regeneration
Summary
Lazy loading is a powerful optimization technique in Next.js applications that can significantly improve performance by:
- Loading components only when they're needed using
dynamic
imports - Automatically lazy loading images that are not in the viewport
- Optimizing fonts to prevent layout shifts
- Leveraging automatic code splitting at the page level
By implementing these techniques, you can create faster, more efficient Next.js applications that provide a better user experience, reduce bounce rates, and potentially improve SEO rankings.
Additional Resources
- Next.js Documentation on Dynamic Imports
- Next.js Image Component Documentation
- Next.js Font Optimization
- Web.dev Guide on Lazy Loading
Exercises
- Convert an existing component in your project to use lazy loading with
dynamic
. - Implement the
next/image
component for all images on a page and test the performance improvements. - Create a tabbed interface where each tab's content is lazy loaded when the tab is clicked.
- Add blur placeholders to improve the user experience while images load.
- Use Chrome DevTools' Network tab to measure the performance improvements after implementing lazy loading in your application.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)