Technology · Next.js
Next.js Error Handling
Implement custom error pages, error boundaries, and global error handling strategies.
TL;DR
- 01Create error.tsx files for segment-level error boundaries.
- 02Create not-found.tsx for 404 pages.
- 03Handle errors in server actions and API routes explicitly.
Error Boundaries with error.tsx
- Create error.tsx for segment-level error handling.
// app/blog/error.tsx "use client"; import { useEffect } from "react"; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { useEffect(() => { console.error(error); }, [error]); return ( <div> <h2>Something went wrong!</h2> <button onClick={() => reset()}>Try again</button> </div> ); } - Error boundaries catch errors from child segments.
- Reset function allows users to retry.
- Must be a client component with "use client" directive.
Custom 404 Pages
- Create not-found.tsx for missing resources.
// app/blog/[slug]/not-found.tsx export default function NotFound() { return ( <div> <h1>Post not found</h1> <p>The post you're looking for doesn't exist.</p> </div> ); } - Use notFound() function to trigger the not-found page.
import { notFound } from "next/navigation"; export default async function Post({ params }) { const post = await getPost(params.slug); if (!post) { notFound(); } return <article>{post.content}</article>; } - Each segment can have its own not-found.tsx.
- Add a root-level not-found.tsx for app-wide 404 handling.
// app/not-found.tsx import Link from "next/link"; export default function RootNotFound() { return ( <div> <h1>Page not found</h1> <Link href="/">Go home</Link> </div> ); } - Export metadata from not-found.tsx for SEO.
export const metadata = { title: "404 — Page not found", description: "This page does not exist." };
Server Action Error Handling
- Catch and handle errors in server actions.
"use server"; export async function createPost(formData: FormData) { try { const title = formData.get("title") as string; if (!title) { return { error: "Title is required" }; } const post = await db.post.create({ data: { title } }); return { success: true, post }; } catch (error) { return { error: "Failed to create post" }; } } - Return error objects from server actions.
- Handle errors in client components.
"use client"; export default function Form() { const [error, setError] = useState<string | null>(null); async function handleSubmit(formData: FormData) { const result = await createPost(formData); if (result.error) { setError(result.error); } } return ( <form action={handleSubmit}> {error && <p>{error}</p>} </form> ); }
API Route Error Handling
- Return error responses with appropriate status codes.
// app/api/posts/[id]/route.ts export async function GET( request: Request, { params }: { params: { id: string } } ) { try { const post = await getPost(params.id); if (!post) { return Response.json( { error: "Post not found" }, { status: 404 } ); } return Response.json(post); } catch (error) { return Response.json( { error: "Internal server error" }, { status: 500 } ); } } - Always handle errors in API routes explicitly.
- Return meaningful status codes and error messages.
- Validate request body fields and return 400 for bad input.
export async function POST(request: Request) { const body = await request.json(); if (!body.title) { return Response.json({ error: "Title is required" }, { status: 400 }); } return Response.json({ ok: true }); } - Return 401 for missing or invalid authentication tokens.
const token = request.headers.get("authorization"); if (!token || !isValid(token)) { return Response.json({ error: "Unauthorized" }, { status: 401 }); }
Global Error Handling
- Create a root error.tsx for unhandled errors.
// app/error.tsx (root level) "use client"; export default function RootError({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { return ( <html> <body> <h1>Application Error</h1> <p>Something went wrong across the application.</p> <button onClick={() => reset()}>Try again</button> </body> </html> ); } - Use error monitoring services like Sentry.
useEffect(() => { Sentry.captureException(error); }, [error]); - Log errors for debugging and monitoring.
- Create a global-error.tsx to catch errors in the root layout.
// app/global-error.tsx "use client"; export default function GlobalError({ reset }) { return ( <html> <body> <h1>Something went wrong</h1> <button onClick={reset}>Try again</button> </body> </html> ); } - Use the error digest to correlate client errors with server logs.
export default function Error({ error }: { error: Error & { digest?: string } }) { return <p>Error ID: {error.digest}</p>; // digest matches the server-side error log entry }
Tip: Use error boundaries at multiple levels to provide context-specific error messages and recovery options for different parts of your app.
Warning: Never expose sensitive error details to users — log detailed errors server-side and show friendly messages to clients.