Next.js loading UI and streaming let you display a skeleton layout immediately while slow server data finishes loading. Instead of waiting for every database query before sending HTML, the server streams page sections as soon as each one is ready.
Adding a loading.tsx file inside any route folder wraps that route's page.tsx in a React <Suspense> boundary automatically. Users see instant visual feedback on navigation.
flowchart TD
A["User Opens /dashboard"] --> B["Send Layout + loading.tsx Skeleton"]
B --> C["Fast Summary Query Completes"]
C --> D["Stream Summary HTML Chunk"]
B --> E["Slow Analytics Query Completes"]
E --> F["Stream Analytics HTML Chunk"]
loading.tsxExport a lightweight skeleton component from loading.tsx. Next.js renders it right away while the async page.tsx fetches data.
// app/dashboard/loading.tsx
export default function DashboardLoading() {
return (
<div className="p-6 space-y-4 animate-pulse">
<div className="h-8 bg-gray-300 rounded w-1/3" />
<div className="h-28 bg-gray-200 rounded" />
<div className="h-28 bg-gray-200 rounded" />
</div>
);
}
<Suspense>A loading.tsx file replaces the entire page segment while loading. For finer control, keep fast content visible right away and wrap only slow async components in React <Suspense>.
// app/dashboard/page.tsx
import { Suspense } from 'react';
import AccountSummary from '@/components/AccountSummary';
import RevenueChart from '@/components/RevenueChart';
export default function DashboardPage() {
return (
<div className="p-6 space-y-6">
<h2 className="text-2xl font-bold">Store Dashboard</h2>
<AccountSummary />
<Suspense fallback={<div className="h-48 bg-gray-100 rounded animate-pulse" />}>
<RevenueChart />
</Suspense>
</div>
);
}
Streaming cuts Time to First Byte (TTFB) and First Contentful Paint (FCP) because the browser starts painting the layout shell immediately. Slow third-party APIs no longer freeze navigation across your whole app.
page.tsx: If page.tsx awaits a slow API call at the top, <Suspense> around a child will not help; move the await inside the child component.loading.tsx to cover layout.tsx: Next.js places loading.tsx inside layout.tsx, so slow fetches inside layout.tsx still block rendering.loading.tsx and <Suspense>?loading.tsx creates an automatic <Suspense> wrapper around the whole page.tsx segment. Manual <Suspense> lets individual widgets stream independently.
Yes, Next.js waits or streams complete HTML chunks properly so search crawlers still index streamed Server Component content.
loading.tsx be a Server Component?Yes, loading.tsx is a Server Component by default, though you can mark it 'use client' if your spinner needs browser state.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With