Part 113: Enhancing Page Performance in Next.js with React Suspense

[App] Streaming with Suspense

[App] Streaming with Suspense

Building a responsive and efficient web application involves optimizing how and when data is loaded and rendered. In this post, we'll dive into how you can improve page load times in a Next.js application by leveraging React's Suspense and Next.js's streaming capabilities.

Current Situation

Our application allows users to submit comments on a review page. Once a comment is submitted, it's saved to a database, and the page updates to reflect the new comment. Currently, the comments are displayed at the bottom of the page, but they are loaded alongside the rest of the page's content. This can slow down the initial rendering, especially if fetching comments from the database is slow.

Why Optimize?

The review page is rendered on the server and sent as a complete HTML document. This includes both the review content and the comments section. If fetching the comments takes time, the entire page's loading is delayed, which affects the user experience.

The Optimization Strategy

To enhance performance, we can defer loading the comments until after the main content is displayed. This allows users to interact with the page more quickly. Here's how you can achieve this using React's Suspense and Next.js's streaming feature.

Step 1: Simulating a Delay

To understand the impact of slow data fetching, let's simulate a delay in fetching comments. This will help us see how the page behaves with and without optimization.

// lib/comments.js
export async function getComments(slug) {
  // simulate delay:
  await new Promise((resolve) => setTimeout(resolve, 3000));
  return await db.comment.findMany({
    where: { slug },
    orderBy: { postedAt: 'desc' },
  });
}

Step 2: Implementing Suspense

We'll wrap the CommentList component in React's Suspense component. This allows us to specify a fallback UI (like a loading indicator) while the comments are being loaded.

// app/reviews/[slug]/page.jsx
import { Suspense } from 'react';
import CommentForm from '@/components/CommentForm';
import CommentList from '@/components/CommentList';

export default async function ReviewPage({ params: { slug } }) {
  // ...other code

  return (
    <>
      {/* ...other content */}
        </h2>
        <CommentForm slug={slug} title={review.title} />
        <Suspense fallback={<p>Loading...</p>}>
          <CommentList slug={slug} />
        </Suspense>
      </section>
    </>
  );
}

How It Works

  • Suspense Component: By wrapping CommentList in Suspense, we defer its loading until after the rest of the page is displayed. The fallback prop provides a UI to display while waiting.

  • Streaming: Next.js leverages streaming to send the page's HTML to the browser as soon as possible. It renders and sends the main content first, and streams the comments when they're ready.

Testing the Optimization

  1. Initial Load: When the page is first opened, the main content (like the review title, image, and body) is displayed immediately.

  2. Loading Indicator: The "Loading..." message is shown where the comments will appear.

  3. Comments Display: After the simulated delay, the comments are fetched and displayed without blocking the initial page load.

Conclusion

By using React's Suspense and Next.js's streaming, we can significantly improve the perceived performance of our application. Users can access and interact with the main content quickly, while less critical data is loaded in the background. This approach not only enhances user experience but also demonstrates the power of modern web technologies in building efficient applications.

This example illustrates how thoughtful design and advanced features like Suspense can create a smoother, more responsive web experience. As web applications grow in complexity, techniques like this become essential for maintaining performance and user satisfaction.

Last updated