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.
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?