Skip to content
Watch the complete Next.js 15 course on YouTube

Loading states

This page explains how to implement loading states for route segments in Next.js using the loading.tsx special file.

Creating a loading state

To implement a loading state, create a loading.tsx file in any route segment where you want loading UI to appear. Next.js automatically wraps the route’s content in a React Suspense boundary.

  • Directoryapp
    • Directoryproducts
      • loading.tsx
      • page.tsx
app/products/loading.tsx
export default function ProductListLoading() {
return <h1>Loading products...</h1>;
}

The loading component displays while the page content loads, then gets replaced once content is ready.

Nested loading states

Loading states can be nested within route segments. Each loading.tsx file creates a Suspense boundary for its segment:

app/products/[id]/loading.tsx
export default function ProductLoading() {
return <h1>Loading products...</h1>;
}

Streaming with Suspense

For more granular control, use Suspense directly in your components:

app/products/[id]/page.tsx
import { Suspense } from "react";
import Loading from "./loading";
export default function ProductPage() {
return (
<div>
<h1>Product Details</h1>
<Suspense fallback={<Loading />}>
<ProductInfo />
</Suspense>
<Suspense fallback={<Loading />}>
<ProductReviews />
</Suspense>
</div>
);
}

Customizing the loading UI

While the examples above show basic text as loading UI, you can enhance the user experience with:

  • Skeleton loaders
  • Spinners
  • Content previews (like image or title placeholders)

Benefits of loading UI

  1. Instant feedback: Users receive immediate visual feedback during navigation, making the application feel responsive.

  2. Interactive layouts: Shared layouts remain interactive while new content loads — users can continue to use navigation menus and sidebars during content loading.

Example with artificial delay

The loading state might not be visible on fast-loading pages. Here’s an example that adds an artificial delay to demonstrate the loading behavior:

app/products/page.tsx
export default async function Products() {
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 2000);
});
return <h1>Products page</h1>;
}

With this delay, the loading UI becomes visible for 2 seconds before the products content appears.

Good to know

  • Loading UI appears instantly on navigation
  • Layouts remain interactive during loading
  • Loading states can be nested for granular feedback
  • Works automatically with React Suspense
  • Useful for both SSR and client-side transitions