← Back to chapters
12intermediate

Loading & Error Handling

loading.tsx, error.tsx, not-found.tsx, and Suspense boundaries for better UX.

Loading & Error UI: Built Into the Router

Next.js has special files that automatically create loading states, error boundaries, and 404 pages. No manual Suspense or ErrorBoundary setup needed (though you can still use those too).

loading.tsx: Instant Loading UI

src/app/dashboard/loading.tsx
tsx
1// src/app/dashboard/loading.tsx
2// Automatically shown while dashboard/page.tsx is loading
3
4export default function DashboardLoading() {
5return (
6 <div className="animate-pulse space-y-4 p-8">
7 <div className="h-8 bg-gray-200 rounded w-1/3" />
8 <div className="h-4 bg-gray-200 rounded w-2/3" />
9 <div className="h-4 bg-gray-200 rounded w-1/2" />
10 <div className="grid grid-cols-3 gap-4 mt-8">
11 <div className="h-32 bg-gray-200 rounded" />
12 <div className="h-32 bg-gray-200 rounded" />
13 <div className="h-32 bg-gray-200 rounded" />
14 </div>
15 </div>
16);
17}

This creates a Suspense boundary automatically. When the page is fetching data, this loading UI shows. When data arrives, it swaps to the actual page. No useState, no isLoading. Just works.

error.tsx: Graceful Error Handling

src/app/dashboard/error.tsx
tsx
1"use client"; // Error components MUST be Client Components
2
3import { useEffect } from "react";
4
5export default function DashboardError({
6error,
7reset,
8}: {
9error: Error & { digest?: string };
10reset: () => void;
11}) {
12useEffect(() => {
13 console.error("Dashboard error:", error);
14}, [error]);
15
16return (
17 <div className="p-8 text-center">
18 <h2 className="text-2xl font-bold text-red-600">Something went wrong!</h2>
19 <p className="mt-2 text-gray-600">{error.message}</p>
20 <button
21 onClick={reset}
22 className="mt-4 px-4 py-2 bg-black text-white font-bold"
23 >
24 Try Again
25 </button>
26 </div>
27);
28}
💡

Error boundaries are scoped

An error.tsx catches errors in its segment and children. So an error in /dashboard/settings won't crash the entire app. Only the dashboard section shows the error UI. The rest of the app stays interactive.

not-found.tsx: Custom 404

src/app/not-found.tsx
tsx
1// src/app/not-found.tsx (global 404)
2import Link from "next/link";
3
4export default function NotFound() {
5return (
6 <div className="min-h-screen flex items-center justify-center">
7 <div className="text-center">
8 <h1 className="text-6xl font-bold">404</h1>
9 <p className="mt-4 text-xl">Page not found</p>
10 <Link href="/" className="mt-6 inline-block px-6 py-3 bg-black text-white font-bold">
11 Go Home
12 </Link>
13 </div>
14 </div>
15);
16}

You can also trigger 404 programmatically with 'notFound()' from next/navigation. Useful when a dynamic route's data doesn't exist.

Watch and Learn