Loading UI & loading.tsx
loading.tsx file into any route segment, and Next.js automatically wraps that segment's page.tsx (and everything nested under it) in a React <Suspense> boundary, using your loading.tsx component as the fallback. You get instant loading states without writing a single <Suspense> tag yourself.How it works
app/dashboard/loading.tsx
export default function Loading() {
return <p>Loading dashboard…</p>
}app/dashboard/page.tsx
import { getDashboardData } from '@/libs/analytics'
export default async function DashboardPage() {
const data = await getDashboardData() // slow-ish query
return <Dashboard data={data} />
}<DashboardPage /> in <Suspense fallback={<Loading />}> inside the parent layout — Next.js just does it for you whenever it sees a loading.tsx file next to a page.tsx.Shown during navigation, not just first load
/dashboard shows the loading state immediately while the new page's data is being fetched on the server, keeping the UI responsive instead of leaving the previous page frozen with no feedback.A worked skeleton example
A skeleton — gray placeholder shapes matching the real layout — usually feels smoother than a spinner or plain text, since it previews the page's structure before content arrives.
app/dashboard/loading.tsx — skeleton
export default function Loading() {
return (
<div>
<div className="skeleton-line" style={{ width: '40%', height: 24 }} />
<div style={{ display: 'flex', gap: 16, marginTop: 16 }}>
<div className="skeleton-box" style={{ width: 200, height: 120 }} />
<div className="skeleton-box" style={{ width: 200, height: 120 }} />
<div className="skeleton-box" style={{ width: 200, height: 120 }} />
</div>
</div>
)
}Granularity at different nesting levels
loading.tsx is scoped to the route segment it's placed in, you can add it at multiple levels of nesting for progressively finer loading states — a top-level layout shows a full-page skeleton for the very first visit, while a nested segment shows a smaller, local loading state when only that inner section is navigating.Nested loading.tsx files
app/
dashboard/
layout.tsx
loading.tsx ← shown while /dashboard itself loads
page.tsx
settings/
loading.tsx ← shown while /dashboard/settings loads
page.tsxloading.tsx is used for a given navigation — navigating straight to /dashboard/settings shows the settings segment's own loading UI, not the dashboard-level one, since the shared parts of the layout above it are already rendered.loading.tsx as the default way to add loading states to whole pages. Use manual <Suspense> boundaries (covered on the previous page) when you need multiple independent loading states within a single page, since a route can only have one loading.tsx.loading.tsx only affects the segment it's placed in and its children — it has no effect on sibling routes, and a parent layout's content above the segment continues to render (and stay interactive) while the segment below it is loading.A
loading.tsxfile in a route segment automatically wraps that segment'spage.tsxin a Suspense boundary, using itself as the fallback.It appears on both first load and client-side navigation into that route.
A skeleton UI matching the real layout typically feels smoother than a spinner.
Nested
loading.tsxfiles give progressively finer loading states as navigation gets deeper into the route tree.Use
loading.tsxfor whole-page loading states; use manual<Suspense>boundaries for multiple independent loading states within one page.