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.tsx2// Automatically shown while dashboard/page.tsx is loading34export 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 Components23import { useEffect } from "react";45export default function DashboardError({6error,7reset,8}: {9error: Error & { digest?: string };10reset: () => void;11}) {12useEffect(() => {13 console.error("Dashboard error:", error);14}, [error]);1516return (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 <button21 onClick={reset}22 className="mt-4 px-4 py-2 bg-black text-white font-bold"23 >24 Try Again25 </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";34export 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 Home12 </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.