AiTechWorlds
AiTechWorlds
error.tsx and not-found.tsx are Next.js file conventions that contain a route's failure to that route instead of letting it crash the whole app.
Think of a building's fire doors: a fire in one office closes behind its own door and never spreads to the rest of the floor. Production apps fail — database connections drop, external APIs return 500s, unexpected data causes JavaScript errors. The question isn't whether errors will happen, it's whether they crash your entire app or stay contained to the section that broke.
Create an error.tsx file alongside any page.tsx to catch errors thrown during rendering or data fetching in that route:
// src/app/error.tsx — global error boundary
"use client"; // error.tsx must be a Client Component
import { useEffect } from "react";
interface ErrorProps {
error: Error & { digest?: string };
reset: () => void;
}
export default function GlobalError({ error, reset }: ErrorProps) {
useEffect(() => {
// Log to an error reporting service
console.error("Unhandled error:", error);
}, [error]);
return (
<main className="min-h-screen flex items-center justify-center px-4">
<div className="max-w-md text-center">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-6">
<svg className="w-8 h-8 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h1 className="text-2xl font-bold text-gray-900">Something went wrong</h1>
<p className="text-gray-500 mt-2">
{process.env.NODE_ENV === "development"
? error.message
: "An unexpected error occurred. We've been notified."}
</p>
{error.digest && (
<p className="text-xs text-gray-400 mt-1">Error ID: {error.digest}</p>
)}
<div className="flex gap-3 justify-center mt-6">
<button
onClick={reset}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold px-5 py-2.5 rounded-lg transition-colors"
>
Try again
</button>
<a
href="/"
className="border border-gray-300 text-gray-700 hover:bg-gray-50 font-semibold px-5 py-2.5 rounded-lg transition-colors"
>
Go home
</a>
</div>
</div>
</main>
);
}The reset function re-renders the error boundary and retries the failed component. The error.digest is a hash of the error for server-side log correlation.
Put error.tsx inside a specific folder to limit the error boundary to that section:
src/app/
├── error.tsx → Catches errors anywhere in the app
├── dashboard/
│ ├── error.tsx → Only catches errors in /dashboard/**
│ ├── page.tsx
│ └── analytics/
│ ├── error.tsx → Only catches errors in /dashboard/analytics
│ └── page.tsxThe analytics error boundary doesn't crash the whole dashboard. An analytics error shows a friendly error UI just for that section while the rest of the dashboard works normally.
When a page calls notFound(), Next.js renders the nearest not-found.tsx:
// src/app/not-found.tsx — shown for any 404
import Link from "next/link";
export default function NotFound() {
return (
<main className="min-h-screen flex items-center justify-center px-4">
<div className="max-w-md text-center">
<h1 className="text-9xl font-black text-gray-100 select-none">404</h1>
<div className="-mt-8">
<h2 className="text-3xl font-bold text-gray-900">Page not found</h2>
<p className="text-gray-500 mt-3">
The page you're looking for doesn't exist or has been moved.
</p>
<div className="flex gap-3 justify-center mt-8">
<Link
href="/"
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold px-5 py-2.5 rounded-lg transition-colors"
>
Go home
</Link>
<Link
href="/courses"
className="border border-gray-300 text-gray-700 hover:bg-gray-50 font-semibold px-5 py-2.5 rounded-lg transition-colors"
>
Browse courses
</Link>
</div>
</div>
</div>
</main>
);
}Trigger it from any server component or API route:
// src/app/courses/[slug]/page.tsx
import { notFound } from "next/navigation";
export default async function CoursePage({ params }: { params: { slug: string } }) {
const course = await getCourse(params.slug);
if (!course) {
notFound(); // renders not-found.tsx with 404 status
}
return <CourseDetail course={course} />;
}Scoped not-found.tsx files work just like scoped error.tsx:
// src/app/courses/not-found.tsx — specific to /courses/**
export default function CourseNotFound() {
return (
<div className="text-center py-16">
<h2 className="text-2xl font-bold">Course not found</h2>
<p className="text-gray-500 mt-2">
This course doesn't exist or has been removed.
</p>
<Link href="/courses" className="mt-6 inline-block text-blue-600 hover:underline">
Browse all courses
</Link>
</div>
);
}// Use notFound() for "this resource doesn't exist"
// → 404 status, renders not-found.tsx
if (!course) notFound();
// Throw an error for unexpected failures
// → 500 status, renders error.tsx
if (!db) throw new Error("Database connection failed");
// Use redirect() for auth failures
// → 307 redirect, no error boundary
if (!user) redirect("/login");// src/app/api/courses/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
try {
const course = await db.course.findUnique({ where: { id: params.id } });
if (!course) {
return NextResponse.json(
{ error: "Course not found" },
{ status: 404 }
);
}
return NextResponse.json(course);
} catch (error) {
console.error("Failed to fetch course:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}If the root layout.tsx itself throws, you need a special global-error.tsx (it replaces the entire <html> in that case):
// src/app/global-error.tsx
"use client";
export default function GlobalError({ error, reset }: { error: Error; reset: () => void }) {
return (
<html>
<body>
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1>Something went catastrophically wrong</h1>
<button onClick={reset}>Try again</button>
</div>
</div>
</body>
</html>
);
}global-error.tsx is a last resort — layout errors are rare. The standard error.tsx handles page-level errors in the vast majority of cases.
Next lesson: Route groups and parallel routes — advanced App Router patterns for complex navigation.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises