← Writing

Streaming UI with Suspense in Next.js

Hello everyone! In this post we're going to discuss streaming UI using Suspense, its benefits, and its effect on SEO.

Demo

Let's start with what we've built: a simple webpage that loads a video from a URL. The important thing to notice is the suggested videos and comments — they arrive as a "streamed" response. The whole page is not built at once; it builds progressively as data keeps arriving from the server.

In our case, first the video starts playing, then two seconds later (a setTimeout mimicking data fetching) suggested videos are fetched and displayed, and after two more seconds the comments load and show up.

Components involved — Suspense

So, what's Suspense? Suspense is a React feature that lets us handle async operations in a declarative, more readable way.

We can wrap components with async dependencies in a <Suspense> component and provide a fallback UI. The fallback is displayed while the async operation completes. It simplifies handling loading states.

How streaming works

Next.js supports server-side rendering, which in short works by fetching the data required by the page on the server, rendering the HTML on the server, and sending it all to the client.

Data fetching is blocking — we need to fetch a page's data before we can render and hydrate it on the client.

But React is a great candidate for streaming because it's based on components, and each component can be treated like a separate page. A component goes through the SSR steps and appears on the client while sibling components on the same page are still fetching their data. This enables progressively building the UI.

Code explanation

This is our SuggestedVideos component that fetches the videos (mocked with setTimeout):

async function getSuggestedVideos() {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return Array.from({ length: 10 }, (_, index) => "Video");
}

export default async function SuggestedVideos() {
  const suggestedVideos = await getSuggestedVideos();
  return suggestedVideos.map((video, index) => {
    return (
      <div
        className={`flex flex-row items-center ${
          index != 0 ? "p-4" : "px-4 pb-4"
        }`}
      >
        <div className="rounded bg-slate-700 w-44 h-20"></div>
        <p className="p-2">Video {index + 1}</p>
      </div>
    );
  });
}

This is our Comments component that fetches the comments:

async function getComments() {
  await new Promise((resolve) => setTimeout(resolve, 4000));
  return Array.from({ length: 20 }, (_, index) => "Comment");
}

export default async function Comments() {
  const comments = await getComments();
  return comments.map((comment, index) => {
    return <p className="p-2">Comment {index + 1}</p>;
  });
}

And this is our Homepage component that shows the video and wraps the comments and suggested videos in <Suspense>:

export default function Homepage() {
  return (
    <div className="flex flex-row justify-between px-20 py-10">
      <div className="pr-6 flex flex-col flex-grow-2 w-2/3">
        <div className="flex flex-col">
          <VideoPlayer></VideoPlayer>
        </div>
        <div className="pt-6">
          <Suspense fallback={<p>Loading comments...</p>}>
            <Comments></Comments>
          </Suspense>
        </div>
      </div>
      <div className="flex-grow">
        <Suspense fallback={<p>Loading suggested videos...</p>}>
          <SuggestedVideos></SuggestedVideos>
        </Suspense>
      </div>
    </div>
  );
}

As in the code above, we're using <Suspense> to wrap our Comments and SuggestedVideos components, both of which fetch data asynchronously before rendering. Full code on GitHub: github.com/UsamaHafeez0/streaming-suspense-and-seo

Main idea and benefits

The fallback should be meaningful UI — it can even carry state, like the name of the video currently loading.

The main idea behind this simple pattern is to improve user experience and perceived loading performance. It's useful wherever a slow request would otherwise block the whole UI from rendering.

Impact on SEO

Streaming doesn't affect SEO in any negative way.

Originally published on Medium.

Have a product to build?

15 minutes, no slides — let's talk about it.