How to implement lazy loading in Next.js using next/dynamic

Key takeaways:

  • Lazy loading reduces the initial load time and resource usage, leading to faster application performance.

  • By improving load times, lazy loading can enhance the SEO of the Next.js applications.

  • In Next.js, lazy loading can be achieved using next/dynamic or React.lazy() with Suspense.

  • The next/dynamic function allows us to dynamically import components, optimizing performance by loading them only when needed.

Next.js is an open-source React framework with server-side rendering (SSR) and static site generation (SSG) capabilities. It is built on React and provides several additional features and optimizations. Lazy loading is a technique that delays loading resources like images, videos, scripts, and other resources until they are needed. For example, a Next.js application could use lazy loading to generate and render the HTML for pages currently viewed by the user. This can reduce the server load and improve users’ response time.

Why lazy loading?

Some benefits of using lazy loading in Next.js are:

  • Lazy loading can improve the performance of Next.js apps by reducing the initial load time and overall utilization of resources.

  • It can help boost the SEO of Next.js applications by speeding up and optimizing their loading times. This can result in higher SERP rankings.

  • Lazy loading can enhance the user experience of Next.js applications by making them more responsive and less laggy. This is because lazy loading only loads resources when necessary, sparing users from waiting for all of the resources on a page to load before interacting with it.

Implementing lazy loading in Next.js

  • Two ways to implement lazy loading in Next.js:

    • Using next/dynamic

    • Using React.lazy() with Suspense

In this Answer, we will explore next/dynamic method to implement lazy loading in Next.js.

Using next/dynamic

To implement lazy loading, we’ll create MyComponent.tsx, located in the pages directory, and demonstrate how to load it only when needed using dynamic imports in the index.tsx file under the components section. Let’s get started!

First, we need to install the next/dynamic package:

npm install next/dynamic
Installing next/dynamic package

Once the installation is completed, we need to import the next/dynamic package into the Next.js component (index.tsx) and hooks to manage side effects (useEffect), create references (useRef), and manage state (useState).

import dynamic from 'next/dynamic';
import React, { useEffect, useRef, useState } from 'react';
Importing next/dynamic package into Next.js component

Next, dynamically import MyComponent. This is the component we want to lazy load. Option specification:

  • ssr: false: Ensures the component is only rendered on the client side.

  • loading: Displays a loading message while the component is being fetched.

const MyComponent = dynamic(() => import('../components/MyComponent'), {
ssr: false,
loading: () => <p>Loading...</p>,
});
Importing the component to lazy load

Declare component MyPage. The following is a typical way to create a React component using arrow function syntax.

const MyPage = () => {
Declaring MyPage component

Next, useState initializes a state variable called isVisible with a default value of false.

  • isVisible: This will track whether the component MyComponent is currently visible in the viewport.

  • setIsVisible: This function will be used to update the isVisible state.

const [isVisible, setIsVisible] = useState(false);
Initialising the state variable isVisible

In the following snippet, the ref will be attached to a DOM element that we want to observe for visibility. Initially, it does not point to any element.

const ref = useRef();
Attaching a DOM element

Next, we set up the Intersection Observer. The useEffect hook runs after the component mounts. It’s where you set up the Intersection Observer.

  • Creating the Observer: An instance of IntersectionObserver is created to monitor the visibility of the element.

  • Callback function: This function is triggered when the observed element's visibility changes.

  • entry.isIntersecting: This checks if the element is currently visible in the viewport.

  • If the element is visible, setIsVisible(true) updates the state, allowing MyComponent to be rendered.

  • observer.disconnect(): Stops the observer from tracking the element after it becomes visible to improve performance.

useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect(); // Stop observing after it has loaded
}
},
{ threshold: 0.1 } // Load when 10% is visible
);
Intersection Observer

If ref.current is valid (i.e., it points to a DOM element), the observer starts monitoring that element for visibility.

if (ref.current) {
observer.observe(ref.current);
}
Checking ref.current

The following function runs when the component unmounts or before the effect runs again. It ensures that the observer stops monitoring the element to prevent memory leaks.

return () => {
if (ref.current) {
observer.unobserve(ref.current);
}
};
Avoiding memory leaks

In the next step, the following code renders the component, displays a welcome message, and instructs the user to scroll. The <div style={{ height: '100vh' }}></div> creates a tall space to enable scrolling. This ensures there’s enough room for the user to scroll down to where MyComponent will be loaded. A ref is attached to a <div> that the Intersection Observer monitors. When the <div> becomes visible (e.g., 10% in view), the observer updates a state variable (isVisible). If isVisible is true, the component (MyComponent) is rendered inside the <div>, optimizing performance by loading it only when needed.

return (
<div>
<h1>Welcome to My Page!</h1>
<p>Scroll down to load the MyComponent...</p>
<div style={{ height: '100vh' }}></div>
<div ref={ref}>
{isVisible && <MyComponent />}
</div>
</div>
);
export default MyPage;
Rendering the loading component

Code implementation: Lazy loading using next/dynamic

The code snippets above can be combined into the following widget. Run the widget to see how next/dynamic works with the Intersection Observer to implement lazy loading in Next.js.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
}

module.exports = nextConfig
Implementating lazy loading using next/dynamic

Lazy loading is a powerful technique that can improve the performance, SEO, bandwidth usage, and user experience of Next.js applications. It is a great way to prevent unnecessary content rendering on a page. By using it, we could minimize the page's initial load time, as the lazy-loaded components will be rendered on user demand only.

Frequently asked questions

Haven’t found what you were looking for? Contact Us


Is Next.js good for dynamic websites?

Yes, it supports server-side rendering and static generation for fast, dynamic content.


How do we implement lazy loading in js?

There are different ways to implement lazy loading using JavaScript.

  • Using the event listener
  • Using the Intersection Observer API

Is Next.js frontend or backend?

It’s primarily a frontend framework with backend capabilities, including API routes.


Free Resources

Copyright ©2025 Educative, Inc. All rights reserved