Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Loading UI and streaming

While a server component waits for data, the visitor sees a blank screen. To avoid that, Next.js can send the page in parts: first whatever is ready plus a placeholder where the slow part goes, then the missing piece. This approach is called streaming.

Slow data

As an experiment, let us make a comments component that takes two seconds to load. Create components/Comments.tsx:

// components/Comments.tsx
export default async function Comments() {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const comments = ['Oleg: Great post!', 'Anna: Thank you!'];
 
  return (
    <ul>
      {comments.map((text) => (
        <li key={text}>{text}</li>
      ))}
    </ul>
  );
}

The delay imitates a slow database query. If you put <Comments /> into the post page as usual, the whole page takes two seconds to open.

The loading.tsx file

The simplest way is a loading.tsx file next to the page:

// app/posts/[slug]/loading.tsx
export default function Loading() {
  return <p>Loading the post...</p>;
}

While the post page is being prepared, "Loading the post..." is shown in its place. Layouts — the header and menu — appear immediately; the placeholder replaces only the page content.

Suspense for part of a page

loading.tsx replaces the whole page. To show the post at once and wait only for the comments, wrap the slow component in React's Suspense:

// app/posts/[slug]/page.tsx
import { Suspense } from 'react';
import { notFound } from 'next/navigation';
import Comments from '@/components/Comments';
import { getPost } from '@/lib/posts';
 
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await getPost(slug);
  if (!post) notFound();
 
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      <Suspense fallback={<p>Loading comments...</p>}>
        <Comments />
      </Suspense>
    </article>
  );
}

Open /posts/hello-nextjs: the post is visible at once, below it "Loading comments...", and two seconds later the list appears in that spot.

How it fits together

ApproachWhat the placeholder replacesWhere it is written
loading.tsxThe whole page of the folderA file next to page.tsx
SuspenseOnly the wrapped partInside the markup

The fallback prop is what to show instead of the content until it is ready. A page can have several Suspense blocks, and each one appears independently.

Common mistake

Loading the slow data in the page itself with await and then passing it to a component inside Suspense. The page still waits, because the await is above. The slow request must run inside the component wrapped in Suspense, like Comments in the example.

Thanks for reading!

Donate to the site — we work hard for you.