Loading, Error and Not-Found UI

Beginner
11 min

Loading, Error and Not-Found UI

A real application spends much of its life in states other than "everything loaded fine": data is still on its way, a request failed, or the URL points at nothing. The App Router gives each state its own file convention. After this lesson you will be able to add loading.tsx, error.tsx, global-error.tsx and not-found.tsx to any route segment and trigger them on purpose.

The Special Files

| File | Rendered when | Notes | |---|---|---| | loading.tsx | The segment's page.tsx is still fetching data | Wraps the page in a React Suspense boundary automatically | | error.tsx | The page or a nested segment throws during render | Must be a Client Component; receives error and reset | | global-error.tsx | The root layout itself throws | Must render its own <html> and <body> | | not-found.tsx | notFound() is called, or no route matches (root only) | Returns HTTP 404 |

Each file applies to its own folder and everything below it.

loading.tsx: Instant Loading States

Create app/dashboard/loading.tsx and export any component:

tsx
// app/dashboard/loading.tsx export default function Loading() { return <div className="skeleton" aria-busy="true">Loading dashboard...</div>; }

Behind the scenes Next.js renders <Suspense fallback={<Loading />}>{page}</Suspense>. The layout and navigation are sent to the browser immediately while the slow page streams in afterwards, and the fallback also appears during client-side navigation. Keep skeletons the same size as the final content to avoid layout shift.

error.tsx: Recoverable Error Boundaries

error.tsx is a React Error Boundary, which is why it must be a Client Component:

tsx
// app/dashboard/error.tsx "use client"; export default function DashboardError({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { return ( <section> <h2>Something went wrong</h2> <p>{error.message}</p> <button onClick={() => reset()}>Try again</button> </section> ); }

Calling reset() re-renders the segment, which is often enough after a transient failure. In production, Next.js strips messages of errors thrown on the server and provides a digest you can match against server logs, so do not rely on error.message for user-facing text.

An error.tsx catches errors from its sibling page.tsx and from all nested segments, but not from the layout.tsx in the same folder. To catch a layout's errors, place error.tsx one level up. The root layout is covered by app/global-error.tsx, a Client Component with the same props that must render its own <html> and <body> tags because it replaces the root layout entirely.

not-found.tsx and the notFound() Function

Two situations produce a 404. When no route matches, Next.js renders app/not-found.tsx. When a route exists but the requested item does not, call notFound() from next/navigation:

tsx
// app/blog/[slug]/page.tsx import { notFound } from "next/navigation"; import { getPost } from "@/lib/posts"; export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; const post = await getPost(slug); if (!post) notFound(); // stops rendering and shows the nearest not-found.tsx return <article>{post.title}</article>; }

notFound() throws a special error, so code after it never runs. The nearest not-found.tsx up the tree renders with a real 404 status code, which matters for search engines.

Tips

  • Put a generic loading.tsx and error.tsx in app/ and override them only where needed.
  • Do not wrap notFound() or redirect() in try/catch: both work by throwing, and a catch would swallow them.
  • Report error.digest to your monitoring service from a useEffect in error.tsx.
Quick Quiz
Question 1 of 3

Why must `error.tsx` start with `"use client"`?

Key Takeaways

  • loading.tsx wraps the segment in Suspense and streams a fallback while the page loads.
  • error.tsx is a client-side Error Boundary with error and reset props; global-error.tsx covers the root.
  • A segment's error.tsx does not catch its own layout's errors; move the boundary up a level.
  • not-found.tsx renders for unmatched URLs and whenever notFound() is called.
  • notFound() and redirect() throw, so keep them outside try/catch blocks.

Next lesson: Route Groups and Project Organization — group routes without affecting URLs and keep a large app/ folder tidy.

Loading, Error and Not-Found UI - Next.js | CodeYourCraft | CodeYourCraft