`loading.js` and Instant Loading States Explained

loading.js wraps a route in a Suspense boundary to show instant fallback UI. Learn where it goes, what it wraps, and its main caveat.

7 min read

loading.js in Next.js is a special file that gives a route segment an instant loading state. Next.js wraps the segment's page in a Suspense boundary using this component as the fallback, so the fallback appears immediately while the page content streams in.

Where the file goes

Placement is what activates this convention: the file only covers the segment it sits in. Place loading.js next to the page it belongs to, so for a dashboard route that means app/dashboard/loading.tsx alongside app/dashboard/page.tsx.

App.tsxApp.tsx
// app/dashboard/loading.tsx
export default function Loading() {
  return <p aria-live="polite">Loading dashboard</p>;
}

When a user navigates to the dashboard, this fallback renders first, then the page swaps in once its data is ready.

What loading.js wraps

loading.js wraps the page, not-found, and any nested layout in its segment. It does not wrap the segment's own layout, template, or error file.

FileWrapped by loading.js
page.jsYes
not-found.jsYes
nested layout.jsYes
layout.jsNo
error.jsNo

A slow layout still blocks the fallback unless its data fetch is moved into the page or wrapped in its own Suspense boundary. Because loading.js wraps the page and nested layouts, one fallback covers everything inside the segment below the current layout.

Building a skeleton fallback

A good fallback mirrors the shape of the real content, so the swap feels smooth. For a list, render a few gray bars; for a profile, render the avatar slot and name line. Keep it light, because this component is what every navigation shows while the real page loads.

The fallback is a Server Component by default. Add the use client directive only when the loading UI needs interactivity, which most skeletons do not. A static skeleton also avoids layout shift, because the space is reserved before the real content arrives.

The layout caveat

If the layout reads runtime data such as cookies, headers, or an uncached fetch, loading.js cannot show a fallback for it. What happens next depends on the caching model.

Without Cache Components, navigation simply blocks until the layout finishes rendering, which is the most common reason a loading state appears to do nothing. With Cache Components enabled in Next.js 16, the uncached access must be wrapped in its own Suspense boundary, and Next.js raises a build-time error until it is.

Either way the fix is the same: move the personalization into the page, or wrap it in a Suspense boundary inside the layout, so the shell can render instantly.

Instant loading states on navigation

On navigation, the fallback is prefetched, so it appears immediately unless the prefetch has not finished yet. Navigation stays interruptible, meaning the user can move to another route before this one loads, and shared layouts remain interactive while the new segment renders. For the mechanism behind this, see streaming in Next.js, and for finer control see Suspense boundaries.

Rune AI

Rune AI

Key Insights

  • loading.js shows fallback UI while a segment streams in.
  • It wraps the page, not-found, and nested layouts in Suspense.
  • It does not wrap the layout, template, or error file.
  • The fallback is prefetched, so navigation stays instant.
  • Slow layout data must be moved into the page or wrapped in Suspense.
RunePowered by Rune AI

Frequently Asked Questions

Does loading.js wrap the layout too?

No. It wraps the page, not-found, and nested layouts, but not the layout, template, or error file in its own segment.

Can loading.js be a Client Component?

Yes. It is a Server Component by default, but you can add the use client directive if the fallback needs interactivity.

Why is my fallback not showing?

Check whether the segment's layout reads runtime data such as cookies or an uncached fetch. loading.js does not cover the layout, so move the work into the page or wrap it in its own Suspense boundary.

Conclusion

loading.js gives a route segment an instant fallback by wrapping its page in a Suspense boundary. It does not cover the segment's own layout, so slow layout work must be moved or wrapped separately.