loading.tsx
Add a loading.tsx file to give a route an instant loading state, backed by Suspense.
Create a loading.tsx next to a page.tsx and Next.js automatically wraps that route in a Suspense boundary, showing your loading UI instantly while the server renders the page. It is the easiest way to add a route-level loading skeleton.
How it works
loading.tsxis shown immediately on navigation.- It is replaced by the real page once its data is ready.
- No manual
<Suspense>wiring needed for the whole route.
Example
// app/dashboard/loading.tsx
export default function Loading() {
return <p>Loading dashboardβ¦</p>;
}
// app/dashboard/page.tsx (async, may be slow)
export default async function Dashboard() {
const data = await getSlowStats();
return <Stats data={data} />;
}When to use it
- A products page shows a skeleton grid via loading.tsx while inventory data fetches on the server.
- A user dashboard displays a spinner in loading.tsx so visitors get immediate feedback on slow network conditions.
- A blog post shows a loading placeholder for the article body while the slug-based fetch resolves.
More examples
Simple loading file
Next.js wraps the page in a Suspense boundary and renders this file while the async page resolves.
// app/dashboard/loading.tsx
export default function Loading() {
return <p>Loading dashboard data...</p>;
}Skeleton loading component
Returning skeleton placeholders in loading.tsx gives users a layout preview before real content arrives.
// app/products/loading.tsx
export default function Loading() {
return (
<div className="grid grid-cols-3 gap-4">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="h-48 animate-pulse bg-gray-200 rounded" />
))}
</div>
);
}Loading with layout context
The layout stays visible and mounted while loading.tsx fills the page slot until data is ready.
app/
βββ dashboard/
βββ layout.tsx # stays mounted
βββ loading.tsx # shown while page.tsx resolves
βββ page.tsx # async server component
Discussion