Using Next.js with Suspense to create a loading component - LogRocket Blog (2024)

Editor’s note: This article was reviewed for accuracy by David Omotayo on 19 June 2024 and updated to include recommendations around optimizing loading components, dealing with common issues, incorporating error boundaries, integrating Suspense with third-party libraries, and more.

Using Next.js with Suspense to create a loading component - LogRocket Blog (1)

The Next.js 13 launch in October 2022 teased upcoming support for Suspense, a React feature that lets you delay displaying a component until the children have finished loading. Production-ready support for React Suspense landed in Next.js 13.4 when the App Router was stabilized.

Since then, Next.js has introduced continual updates and improvements to its React Suspense support. Using Suspense in a Next.js project can enhance both DX and app performance, making this concept crucial to understand. In this tutorial, we’ll learn how to create a loading component using Next.js and React Suspense.

Loading screens are a crucial part of any website — by letting the user know that some processing is happening, you can reduce their frustration and decrease the chance that they’ll leave the website. Additionally, by allowing content to load asynchronously, you can improve a website’s UX and performance.

Let’s get started!

What is React Suspense?

React’s Suspense component was first added to React in v16.6, which was released in 2018. Suspense handles asynchronous operations like code splitting and data fetching. In simple terms, it lets you display a fallback component until the child component is fully loaded.

The code below shows React Suspense’s syntax:

<Suspense fallback={<Loading />}> <SomeComponent /></Suspense>

To load multiple components, you can add multiple child components within the <Suspense> component. Suspense improves a website’s performance and user experience, becoming an important part of the React ecosystem.

Using Next.js and React Suspense

The Next.js App Router introduced a new file convention: you can now add all the files and components related to the route in a single directory. This includes both components and CSS files, so there is no need to create a separate directory for CSS files.

In the route directory, you can include the loading.js file to add your loading UI for React Suspense’s fallback component:

Using Next.js with Suspense to create a loading component - LogRocket Blog (2)

Next.js supports server-side rendering, so the UI will take some time to load. In such cases, you can use Suspense to load the UI. The component in loading.js is defined as a functional component that can be exported as the default. The syntax is below:

export default function Loading() { // You can add any UI inside Loading, including a Skeleton. return <LoadingSkeleton />}

The Next.js team has worked to keep support for React Suspense up-to-date and efficient, as well as to enhance the DX of using Suspense with Next. Some of the key improvements include:

  • Use of React Server Components with Suspense
  • Partial pre-rendering
  • Refactored streaming logic

Now, let’s implement Suspense by building a small project in Next.js.

Setting up our Next.js project

We’ll build a web application that uses the TMDB API to fetch trending movies. We have two routes:

  • root(/): Displays a welcome screen in the application
  • movies(/movies): Displays the trending movies fetched from the API

Installing Next.js

You can install Next.js with the command below. Keep in mind that you’ll need to have Node.js pre-installed on your machine:

npx create-next-app@latest --experimental-app

Entering the command above in the terminal will prompt you to answer the following questions:

  • What is your project named?: Name your project whatever you want
  • Would you like to use TypeScript with this project?: No. If you wish, then you can continue with TypeScript, but there won’t be many changes
  • Would you like to use ESLint with this project?: Yes — ESLint will be helpful in debugging errors
  • Would you like to use src/ directory with this project?: No, we’ll use the latest app directory

With that, the project will be automatically set up for you with the necessary packages installed.

Removing unnecessary files and folders

There isn’t a lot of boilerplate code in Next.js, but we should clean it up anyways.

Open the app directory and remove the API directory, which is for the server. Remove all the CSS code from the global.css file in the app directory.

Now, open page.js and remove all the code within the return section. Then, enter the following code in page.js to display a basic page with a welcome message for the user:

async function Page() { return ( <div> <h3>List of trending Movies & TV</h3> </div> );}export default Page;

Now, let’s look at the layout section from the layout.js file:

import { Suspense } from "react";import Link from "next/link";import Loading from "./loading";export const metadata = { title: "Create Next App", description: "Generated by create next app",};export default function RootLayout({ children }) { return ( <html lang="en"> <body> <h1>Trending Movies & Movies</h1> <li> <Link href="/">Home</Link> </li> <li> <Link href="/movies">Movie</Link> </li> <Suspense fallback={<Loading />}>{children}</Suspense> </body> </html> );}

In the code above, we created a navbar in the body section that will be displayed across all the routes. In navbar, we have links to root, movies, and TV routes.

Create a loading.js file

In the root directory, create the loading.js file with the code below:

export default function Loading() { return <p>Loading Data...</p>;}

We’ve created a basic loading component for display, but you can add a much more sophisticated loading screen, like spinners or a skeleton loading screen. The functional component and file naming convention will remain the same.

Creating the movies route

Let’s use the Next.js App Router to create a route. The process is similar to previous Next.js versions. Create a directory within the app directory named movies. Inside movies, create a file named page.js with the code below:

async function getMovies() { let res = await fetch( `https://api.themoviedb.org/3/trending/movie/day?api_key=${process.env.NEXT_PUBLIC_TMDB_API}` ); await new Promise((resolve) => setTimeout(resolve, 2000)); return res.json();}async function Trending() { let { results } = await getMovies(); return ( <div> <h3>Movies</h3> {results && results.map((index) => { return <li>{index.title}</li>; })} </div> );}export default Trending;

Above, we have a two-component file. getMovies() fetches data from the API, which is sent to the default functional component with the name Trending.

Over 200k developers use LogRocket to create better digital experiencesLearn more →

You can see that Trending is a server-side-rendered component. It has an async functional component for promise-based data fetching because Suspense will know that data is being fetched. We’ve also implemented a delay of two seconds to see the loading component properly.

In the Trending component, we call the getMovies() function to get the fetched data. In the return, we are mapping the data to display all the trending movies in a list.

You might find it unusual that we haven’t used Suspense yet. Next.js understands when something is loading; if there is a loading.js file in the route or even the root directory, it will display its loading component when loading occurs.

We can add different loading components separately in every route with the addition of the loading.js file. Let’s check out the following GIF displaying the output:

Using Next.js with Suspense to create a loading component - LogRocket Blog (5)

Optionally, you can also create boundaries with Suspense. Using these, if any component isn’t fully loaded, then the loading component will be displayed. For better performance, this <Suspense> component can be in the layout and page components.

Common issues when using Suspense with error boundaries

There are several common issues and pitfalls that can arise when using a Suspense component and integrating error boundaries. Understanding these can aid in debugging and optimizing your code more effectively. Here are some of the common issues and best practices to avoid them.

Not wrapping Suspense components with error boundaries

If a component within a Suspense boundary throws an error and there is no error boundary to catch it at all, the entire component tree can unmount, meaning all components disappear from the UI. To prevent this, always wrap Suspense components with an ErrorBoundary to handle errors efficiently:

import React, { Suspense } from 'react'; import ErrorBoundary from './ErrorBoundary'; import MyComponent from './MyComponent'; function App() { return ( <ErrorBoundary> <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> </ErrorBoundary> )}; export default App;

Using Suspense without a fallback

Suspense requires a fallback to display while the component is loading. So, if you don’t provide a fallback prop to Suspense, you’ll get an error. Always provide a fallback prop to ensure smooth loading of components:

<Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense>

Nesting Suspense and error boundaries

Using Suspense and error boundaries without a clear strategy can result in unpredictable behavior and make it hard to trace errors or manage loading states.

To avoid this, plan the way you nest Suspense and error boundaries carefully. Ensure each Suspense component has a fallback, like a loading spinner, and place error boundaries where they can catch errors effectively:

<ErrorBoundary> <Suspense fallback={<div>Loading parent...</div>}> <ParentComponent> <ErrorBoundary> <Suspense fallback={<div>Loading child...</div>}> <ChildComponent /> </Suspense> </ErrorBoundary> </ParentComponent> </Suspense></ErrorBoundary>

How concurrent rendering affects React suspense usage

Concurrent rendering is a key feature introduced in React 18 to improve the performance and responsiveness of React applications. It allows React to work on multiple tasks simultaneously and prioritize important updates. This means React can pause or interrupt low-priority rendering processes to handle urgent tasks, like responding to user input.

More great articles from LogRocket:

  • Don't miss a moment with The Replay, a curated newsletter from LogRocket
  • Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
  • Use React's useEffect to optimize your application's performance
  • Switch between multiple versions of Node
  • Discover how to use the React children prop with TypeScript
  • Explore creating a custom mouse cursor with CSS
  • Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Since Suspense “suspends” components until their data is ready, it blocks the entire UI. However, with React’s concurrent rendering, Suspense can better manage loading states. Instead of blocking the entire UI, it can selectively suspend parts of the component tree, keeping the rest of the UI interactive.

Optimizing loading components

Loading components are meant to enhance the overall user experience of an application. However, if implemented incorrectly or sub-optimally, these loading components can negatively impact performance. Let’s discuss some best practices for optimizing loading components in Next.

Lazy loading components

Break your application into smaller chunks that can be loaded on demand rather than loading the entire application at once. You can do this using the Next.js dynamic function like so:

import dynamic from 'next/dynamic';const MyComponent = dynamic(() => import('./MyComponent'));function App() { return ( <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> );}

Lightweight fallback UI

Ensure fallback components used during loading are lightweight and minimal. Avoid heavy operations or complex structures in fallback components. Instead, use simple and fast-rendering components. In most cases, a single div is enough:

<Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense>

Use efficient data fetching strategies

You can reduce wait times and avoid blocking the main thread by optimizing data fetching with libraries like SWR or React Query. These libraries prevents unnecessary re-fetching of data by caching fetch response. Additionally, implementing lazy loading and pagination for large datasets can further improve performance.

Avoid suspending the whole app

Implementing Suspense at the root level of your application will cause the entire application to suspend. Instead, apply Suspense only to parts of the UI that depend on the data being loaded.

Using the startTransition API

With concurrent rendering enabled, you can use the startTransition API to mark updates as ‘transition updates’, allowing React to give them lower priority. This ensures that these updates do not block or interrupt higher-priority updates.

Memoization

Memoizing expensive computations caches their results, preventing unnecessary re-renders and improving the performance of components during suspenseful loading.

Using Suspense with SSG in Next.js

Static site generation (SSG) pre-renders the contents and static assets for each page of a website at build time. This approach inherently conflicts with waiting for asynchronous data using Suspense, as there is typically no need to load additional content.

However, if your statically generated page needs to display frequently updated data, and the page content changes with every request, consider using client-side rendering or partial pre-rendering instead. This approach populates the page content dynamically. For more information, check out the Next.js docs on data fetching.

Integrating Suspense with SWR in Next.js

Data fetching libraries like SWR offer a powerful and efficient solution for managing remote data fetching, caching, and revalidation in Next.js applications. With just a single line of code, you can simplify the logic of data fetching while providing the benefits of reusable data fetching, caching, and avoiding request duplication:

const { data, error, isLoading } = useSWR('/api/user', fetcher);

While SWR offers fetch request states such as success, loading, and error for gracefully handling boundaries in the traditional way, it also provides the option to use React Suspense, with additional benefits:

const { data } = useSWR('/api/user', fetcher, { suspense: true })

In Suspense mode, the fetch response is always available in the data variable and is guaranteed to be ready on render without needing to explicitly check if the data is undefined.

However, Suspense itself doesn’t handle errors that may occur during data fetching. If an error occurs during the fetch request, the promise returned by the data fetching function will be rejected.

To handle errors effectively, always nest your Suspense component within an ErrorBoundary:

import { Suspense } from 'react'import useSWR from 'swr'function MyComponent () { const { data } = useSWR(url, fetcher, { suspense: true }) return <div>hello, {data.title}</div>}function App () { return ( <ErrorBoundary fallback={<h2>Could not fetch posts.</h2>}> <Suspense fallback={<h1>Loading posts...</h1>}> <MyComponent /> </Suspense> </ErrorBoundary> )}

Integrating Suspense with a library like SWR provides a powerful combination for efficient data fetching and smooth user experiences. By leveraging Suspense’s blocking, SWR’s caching, and error boundary, you can build resilient and responsive UIs that efficiently handle loading states and errors.

Conclusion

Building a loading component with Next.js and React Suspense can significantly improve the user experience of your web application. With Suspense, you can easily create loading states for data-fetching and dynamic imports, making your application more responsive and efficient.

By following the steps outlined in this article, you can create a smooth loading experience for your users, reducing frustration and improving engagement. Whether you’re building a simple website or a complex application, using Next.js and React Suspense can help you create a more seamless and enjoyable user experience.

Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to getan app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, notserver-side

    • npm
    • Script tag
    $ npm i --save logrocket // Code:import LogRocket from 'logrocket'; LogRocket.init('app/id'); 
    // Add to your HTML:<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script><script>window.LogRocket && window.LogRocket.init('app/id');</script> 
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin

Get started now

Using Next.js with Suspense to create a loading component - LogRocket Blog (2024)

References

Top Articles
Latest Posts
Article information

Author: Ms. Lucile Johns

Last Updated:

Views: 5445

Rating: 4 / 5 (61 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Ms. Lucile Johns

Birthday: 1999-11-16

Address: Suite 237 56046 Walsh Coves, West Enid, VT 46557

Phone: +59115435987187

Job: Education Supervisor

Hobby: Genealogy, Stone skipping, Skydiving, Nordic skating, Couponing, Coloring, Gardening

Introduction: My name is Ms. Lucile Johns, I am a successful, friendly, friendly, homely, adventurous, handsome, delightful person who loves writing and wants to share my knowledge and understanding with you.