← All lessons
Lesson 08 · Intermediate

Loading UI & Streaming

Show a skeleton instantly while the rest of the page is still loading on the server.

A server-rendered page is only as fast as the slowest fetch inside it. If a page needs five seconds of API work before it has anything to show, the user stares at a blank tab. The fix is streaming: send the parts of the page that are ready, and stream in the rest as it finishes.

loading.js

Add a loading.js file in a route folder. Next.js will show that file instantly on navigation and stream in the actual page when ready.

// app/dashboard/loading.js
export default function Loading() {
  return <p>Loading dashboard…</p>;
}

// app/dashboard/page.js
export default async function Dashboard() {
  const data = await slowFetch();        // takes a second or two
  return <Charts data={data} />;
}

Under the hood, loading.js wraps your page in a React <Suspense> boundary and provides the fallback. You can also drop your own <Suspense> boundaries inside a page to stream parts of it independently:

Granular streaming with Suspense

import { Suspense } from "react";
import Sales from "./Sales";
import Activity from "./Activity";

export default function Dashboard() {
  return (
    <>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>Loading sales…</p>}>
        <Sales />
      </Suspense>
      <Suspense fallback={<p>Loading activity…</p>}>
        <Activity />
      </Suspense>
    </>
  );
}

Each <Suspense> boundary streams independently. A slow Activity won't hold up Sales. This is one of the biggest wins of the App Router.

What to put in a fallback

  • Skeleton blocks matching the shape of the real content — easier on the eye than a spinner.
  • Reserve space so the page doesn't jump when content arrives.
  • Don't fetch in the fallback. Keep it cheap so it renders instantly.
Note: Streaming only helps when there's work to wait on. A page that returns instantly will never show the loading UI.

Try it

Wrap <ExpensiveChart /> in a Suspense boundary with a `Loading chart…` fallback so it streams independently.

Need a hint?

Import Suspense from 'react' and wrap the slow component.

Quiz

Pick the best answer. You only get one shot per question.

1. What does `loading.js` in a route folder do?

2. Two slow components are rendered in the same page. You want them to load independently. What do you do?

3. What should a good Suspense fallback look like?