Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Apply Next.js best practices for RSC boundaries, async APIs, routing, metadata, and optimization.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
error-handling.md
1# Error Handling23Handle errors gracefully in Next.js applications.45Reference: https://nextjs.org/docs/app/getting-started/error-handling67## Error Boundaries89### `error.tsx`1011Catches errors in a route segment and its children:1213```tsx14'use client'1516export default function Error({17error,18reset,19}: {20error: Error & { digest?: string }21reset: () => void22}) {23return (24<div>25<h2>Something went wrong!</h2>26<button onClick={() => reset()}>Try again</button>27</div>28)29}30```3132**Important:** `error.tsx` must be a Client Component.3334### `global-error.tsx`3536Catches errors in root layout:3738```tsx39'use client'4041export default function GlobalError({42error,43reset,44}: {45error: Error & { digest?: string }46reset: () => void47}) {48return (49<html>50<body>51<h2>Something went wrong!</h2>52<button onClick={() => reset()}>Try again</button>53</body>54</html>55)56}57```5859**Important:** Must include `<html>` and `<body>` tags.6061## Server Actions: Navigation API Gotcha6263**Do NOT wrap navigation APIs in try-catch.** They throw special errors that Next.js handles internally.6465Reference: https://nextjs.org/docs/app/api-reference/functions/redirect#behavior6667```tsx68'use server'6970import { redirect } from 'next/navigation'71import { notFound } from 'next/navigation'7273// Bad: try-catch catches the navigation "error"74async function createPost(formData: FormData) {75try {76const post = await db.post.create({ ... })77redirect(`/posts/${post.id}`) // This throws!78} catch (error) {79// redirect() throw is caught here - navigation fails!80return { error: 'Failed to create post' }81}82}8384// Good: Call navigation APIs outside try-catch85async function createPost(formData: FormData) {86let post87try {88post = await db.post.create({ ... })89} catch (error) {90return { error: 'Failed to create post' }91}92redirect(`/posts/${post.id}`) // Outside try-catch93}9495// Good: Re-throw navigation errors96async function createPost(formData: FormData) {97try {98const post = await db.post.create({ ... })99redirect(`/posts/${post.id}`)100} catch (error) {101if (error instanceof Error && error.message === 'NEXT_REDIRECT') {102throw error // Re-throw navigation errors103}104return { error: 'Failed to create post' }105}106}107```108109Same applies to:110- `redirect()` - 307 temporary redirect111- `permanentRedirect()` - 308 permanent redirect112- `notFound()` - 404 not found113- `forbidden()` - 403 forbidden114- `unauthorized()` - 401 unauthorized115116Use `unstable_rethrow()` to re-throw these errors in catch blocks:117118```tsx119import { unstable_rethrow } from 'next/navigation'120121async function action() {122try {123// ...124redirect('/success')125} catch (error) {126unstable_rethrow(error) // Re-throws Next.js internal errors127return { error: 'Something went wrong' }128}129}130```131132## Redirects133134```tsx135import { redirect, permanentRedirect } from 'next/navigation'136137// 307 Temporary - use for most cases138redirect('/new-path')139140// 308 Permanent - use for URL migrations (cached by browsers)141permanentRedirect('/new-url')142```143144## Auth Errors145146Trigger auth-related error pages:147148```tsx149import { forbidden, unauthorized } from 'next/navigation'150151async function Page() {152const session = await getSession()153154if (!session) {155unauthorized() // Renders unauthorized.tsx (401)156}157158if (!session.hasAccess) {159forbidden() // Renders forbidden.tsx (403)160}161162return <Dashboard />163}164```165166Create corresponding error pages:167168```tsx169// app/forbidden.tsx170export default function Forbidden() {171return <div>You don't have access to this resource</div>172}173174// app/unauthorized.tsx175export default function Unauthorized() {176return <div>Please log in to continue</div>177}178```179180## Not Found181182### `not-found.tsx`183184Custom 404 page for a route segment:185186```tsx187export default function NotFound() {188return (189<div>190<h2>Not Found</h2>191<p>Could not find the requested resource</p>192</div>193)194}195```196197### Triggering Not Found198199```tsx200import { notFound } from 'next/navigation'201202export default async function Page({ params }: { params: Promise<{ id: string }> }) {203const { id } = await params204const post = await getPost(id)205206if (!post) {207notFound() // Renders closest not-found.tsx208}209210return <div>{post.title}</div>211}212```213214## Error Hierarchy215216Errors bubble up to the nearest error boundary:217218```219app/220├── error.tsx # Catches errors from all children221├── blog/222│ ├── error.tsx # Catches errors in /blog/*223│ └── [slug]/224│ ├── error.tsx # Catches errors in /blog/[slug]225│ └── page.tsx226└── layout.tsx # Errors here go to global-error.tsx227```228