Skip to content

Commit c527168

Browse files
Getting Started docs: Add Error Handling page (vercel#74069)
Closes: https://linear.app/vercel/issue/DOC-4077/page-handling-errors
1 parent 67ac8bf commit c527168

File tree

3 files changed

+333
-25
lines changed

3 files changed

+333
-25
lines changed

docs/01-app/01-getting-started/02-project-structure.mdx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,17 @@ Top-level files are used to configure your application, manage dependencies, run
5252

5353
### Routing Files
5454

55-
| | | |
56-
| ------------------------------------------------------------------------------- | ------------------- | ---------------------------- |
57-
| [`layout`](/docs/app/api-reference/file-conventions/layout) | `.js` `.jsx` `.tsx` | Layout |
58-
| [`page`](/docs/app/api-reference/file-conventions/page) | `.js` `.jsx` `.tsx` | Page |
59-
| [`loading`](/docs/app/api-reference/file-conventions/loading) | `.js` `.jsx` `.tsx` | Loading UI |
60-
| [`not-found`](/docs/app/api-reference/file-conventions/not-found) | `.js` `.jsx` `.tsx` | Not found UI |
61-
| [`error`](/docs/app/api-reference/file-conventions/error) | `.js` `.jsx` `.tsx` | Error UI |
62-
| [`global-error`](/docs/app/api-reference/file-conventions/error#global-errorjs) | `.js` `.jsx` `.tsx` | Global error UI |
63-
| [`route`](/docs/app/api-reference/file-conventions/route) | `.js` `.ts` | API endpoint |
64-
| [`template`](/docs/app/api-reference/file-conventions/template) | `.js` `.jsx` `.tsx` | Re-rendered layout |
65-
| [`default`](/docs/app/api-reference/file-conventions/default) | `.js` `.jsx` `.tsx` | Parallel route fallback page |
55+
| | | |
56+
| ----------------------------------------------------------------------------- | ------------------- | ---------------------------- |
57+
| [`layout`](/docs/app/api-reference/file-conventions/layout) | `.js` `.jsx` `.tsx` | Layout |
58+
| [`page`](/docs/app/api-reference/file-conventions/page) | `.js` `.jsx` `.tsx` | Page |
59+
| [`loading`](/docs/app/api-reference/file-conventions/loading) | `.js` `.jsx` `.tsx` | Loading UI |
60+
| [`not-found`](/docs/app/api-reference/file-conventions/not-found) | `.js` `.jsx` `.tsx` | Not found UI |
61+
| [`error`](/docs/app/api-reference/file-conventions/error) | `.js` `.jsx` `.tsx` | Error UI |
62+
| [`global-error`](/docs/app/api-reference/file-conventions/error#global-error) | `.js` `.jsx` `.tsx` | Global error UI |
63+
| [`route`](/docs/app/api-reference/file-conventions/route) | `.js` `.ts` | API endpoint |
64+
| [`template`](/docs/app/api-reference/file-conventions/template) | `.js` `.jsx` `.tsx` | Re-rendered layout |
65+
| [`default`](/docs/app/api-reference/file-conventions/default) | `.js` `.jsx` `.tsx` | Parallel route fallback page |
6666

6767
### Nested routes
6868

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
---
2+
title: How to handle errors
3+
nav_title: Error handling
4+
description: Learn how to display expected errors and handle uncaught exceptions.
5+
related:
6+
title: API Reference
7+
description: Learn more about the features mentioned in this page by reading the API Reference.
8+
links:
9+
- app/api-reference/functions/redirect
10+
- app/api-reference/file-conventions/error
11+
- app/api-reference/functions/not-found
12+
- app/api-reference/file-conventions/not-found
13+
---
14+
15+
Errors can be divided into two categories: [expected errors](#handling-expected-errors) and [uncaught exceptions](#handling-uncaught-exceptions). This page will walk you through how you can handle these errors in your Next.js application.
16+
17+
## Handling expected errors
18+
19+
Expected errors are those that can occur during the normal operation of the application, such as those from [server-side form validation](/docs/app/building-your-application/data-fetching/server-actions-and-mutations#server-side-form-validation) or failed requests. These errors should be handled explicitly and returned to the client.
20+
21+
### Server Actions
22+
23+
You can use the [`useActionState`](https://react.dev/reference/react/useActionState) hook to manage the state of [Server Functions](https://react.dev/reference/rsc/server-functions) and handle expected errors. Avoid using `try`/`catch` blocks for expected errors. Instead, you can model expected errors as return values, not as thrown exceptions.
24+
25+
```ts filename="app/actions.ts" switcher
26+
'use server'
27+
28+
export async function createPost(prevState: any, formData: FormData) {
29+
const title = formData.get('title')
30+
const content = formData.get('content')
31+
32+
const res = await fetch('https://api.vercel.app/posts', {
33+
method: 'POST',
34+
body: { title, content },
35+
})
36+
const json = await res.json()
37+
38+
if (!res.ok) {
39+
return { message: 'Failed to create post' }
40+
}
41+
}
42+
```
43+
44+
```js filename="app/actions.js" switcher
45+
'use server'
46+
47+
export async function createPost(prevState, formData) {
48+
const title = formData.get('title')
49+
const content = formData.get('content')
50+
51+
const res = await fetch('https://api.vercel.app/posts', {
52+
method: 'POST',
53+
body: { title, content },
54+
})
55+
const json = await res.json()
56+
57+
if (!res.ok) {
58+
return { message: 'Failed to create post' }
59+
}
60+
}
61+
```
62+
63+
Then, you can pass your action to the `useActionState` hook and use the returned `state` to display an error message.
64+
65+
```tsx filename="app/ui/form.tsx" highlight={11,19} switcher
66+
'use client'
67+
68+
import { useActionState } from 'react'
69+
import { createUser } from '@/app/actions'
70+
71+
const initialState = {
72+
message: '',
73+
}
74+
75+
export function Form() {
76+
const [state, formAction, pending] = useActionState(createPost, initialState)
77+
78+
return (
79+
<form action={formAction}>
80+
<label htmlFor="title">Title</label>
81+
<input type="text" id="title" name="title" required />
82+
<label htmlFor="content">Content</label>
83+
<textarea id="content" name="content" required />
84+
{state?.message && <p aria-live="polite">{state.message}</p>}
85+
<button disabled={pending}>Create Post</button>
86+
</form>
87+
)
88+
}
89+
```
90+
91+
```jsx filename="app/ui/form.js" highlight={11,19} switcher
92+
'use client'
93+
94+
import { useActionState } from 'react'
95+
import { createPost } from '@/app/actions'
96+
97+
const initialState = {
98+
message: '',
99+
}
100+
101+
export function Form() {
102+
const [state, formAction, pending] = useActionState(createPost, initialState)
103+
104+
return (
105+
<form action={formAction}>
106+
<label htmlFor="title">Title</label>
107+
<input type="text" id="title" name="title" required />
108+
<label htmlFor="content">Content</label>
109+
<textarea id="content" name="content" required />
110+
{state?.message && <p aria-live="polite">{state.message}</p>}
111+
<button disabled={pending}>Create Post</button>
112+
</form>
113+
)
114+
}
115+
```
116+
117+
### Server Components
118+
119+
When fetching data inside of a Server Component, you can use the response to conditionally render an error message or [`redirect`](/docs/app/api-reference/functions/redirect).
120+
121+
```tsx filename="app/page.tsx" switcher
122+
export default async function Page() {
123+
const res = await fetch(`https://...`)
124+
const data = await res.json()
125+
126+
if (!res.ok) {
127+
return 'There was an error.'
128+
}
129+
130+
return '...'
131+
}
132+
```
133+
134+
```jsx filename="app/page.js" switcher
135+
export default async function Page() {
136+
const res = await fetch(`https://...`)
137+
const data = await res.json()
138+
139+
if (!res.ok) {
140+
return 'There was an error.'
141+
}
142+
143+
return '...'
144+
}
145+
```
146+
147+
### Not found
148+
149+
You can call the [`notFound`](/docs/app/api-reference/functions/not-found) function within a route segment and use the [`not-found.js`](/docs/app/api-reference/file-conventions/not-found) file to show a 404 UI.
150+
151+
```tsx filename="app/blog/[slug]/page.tsx" switcher
152+
import { getPostBySlug } from '@/lib/posts'
153+
154+
export default function Page({ params }: { params: { slug: string } }) {
155+
const post = getPostBySlug(params.slug)
156+
157+
if (!post) {
158+
notFound()
159+
}
160+
161+
return <div>{post.title}</div>
162+
}
163+
```
164+
165+
```jsx filename="app/blog/[slug]/page.js" switcher
166+
import { getPostBySlug } from '@/lib/posts'
167+
168+
export default function Page({ params }) {
169+
const post = getPostBySlug(params.slug)
170+
171+
if (!post) {
172+
notFound()
173+
}
174+
175+
return <div>{post.title}</div>
176+
}
177+
```
178+
179+
```tsx filename="app/blog/[slug]/not-found.tsx" switcher
180+
export default function NotFound() {
181+
return <div>404 - Page Not Found</div>
182+
}
183+
```
184+
185+
```jsx filename="app/blog/[slug]/not-found.js" switcher
186+
export default function NotFound() {
187+
return <div>404 - Page Not Found</div>
188+
}
189+
```
190+
191+
## Handling uncaught exceptions
192+
193+
Uncaught exceptions are unexpected errors that indicate bugs or issues that should not occur during the normal flow of your application. These should be handled by throwing errors, which will then be caught by error boundaries.
194+
195+
### Nested error boundaries
196+
197+
Next.js uses error boundaries to handle uncaught exceptions. Error boundaries catch errors in their child components and display a fallback UI instead of the component tree that crashed.
198+
199+
Create an error boundary by adding an [`error.js`](/docs/app/api-reference/file-conventions/error) file inside a route segment and exporting a React component:
200+
201+
```tsx filename="app/dashboard/error.tsx" switcher
202+
'use client' // Error boundaries must be Client Components
203+
204+
import { useEffect } from 'react'
205+
206+
export default function Error({
207+
error,
208+
reset,
209+
}: {
210+
error: Error & { digest?: string }
211+
reset: () => void
212+
}) {
213+
useEffect(() => {
214+
// Log the error to an error reporting service
215+
console.error(error)
216+
}, [error])
217+
218+
return (
219+
<div>
220+
<h2>Something went wrong!</h2>
221+
<button
222+
onClick={
223+
// Attempt to recover by trying to re-render the segment
224+
() => reset()
225+
}
226+
>
227+
Try again
228+
</button>
229+
</div>
230+
)
231+
}
232+
```
233+
234+
```jsx filename="app/dashboard/error.js" switcher
235+
'use client' // Error boundaries must be Client Components
236+
237+
import { useEffect } from 'react'
238+
239+
export default function Error({ error, reset }) {
240+
useEffect(() => {
241+
// Log the error to an error reporting service
242+
console.error(error)
243+
}, [error])
244+
245+
return (
246+
<div>
247+
<h2>Something went wrong!</h2>
248+
<button
249+
onClick={
250+
// Attempt to recover by trying to re-render the segment
251+
() => reset()
252+
}
253+
>
254+
Try again
255+
</button>
256+
</div>
257+
)
258+
}
259+
```
260+
261+
Errors will bubble up to the nearest parent error boundary. This allows for granular error handling by placing `error.tsx` files at different levels in the [route hierarchy](/docs/app/getting-started/project-structure#component-hierarchy).
262+
263+
<Image
264+
alt="Nested Error Component Hierarchy"
265+
srcLight="/docs/light/nested-error-component-hierarchy.png"
266+
srcDark="/docs/dark/nested-error-component-hierarchy.png"
267+
width="1600"
268+
height="687"
269+
/>
270+
271+
### Global errors
272+
273+
While less common, you can handle errors in the root layout using the [`global-error.js`](/docs/app/api-reference/file-conventions/error#global-error) file, located in the root app directory, even when leveraging [internationalization](/docs/app/building-your-application/routing/internationalization). Global error UI must define its own `<html>` and `<body>` tags, since it is replacing the root layout or template when active.
274+
275+
```tsx filename="app/global-error.tsx" switcher
276+
'use client' // Error boundaries must be Client Components
277+
278+
export default function GlobalError({
279+
error,
280+
reset,
281+
}: {
282+
error: Error & { digest?: string }
283+
reset: () => void
284+
}) {
285+
return (
286+
// global-error must include html and body tags
287+
<html>
288+
<body>
289+
<h2>Something went wrong!</h2>
290+
<button onClick={() => reset()}>Try again</button>
291+
</body>
292+
</html>
293+
)
294+
}
295+
```
296+
297+
```jsx filename="app/global-error.js" switcher
298+
'use client' // Error boundaries must be Client Components
299+
300+
export default function GlobalError({ error, reset }) {
301+
return (
302+
// global-error must include html and body tags
303+
<html>
304+
<body>
305+
<h2>Something went wrong!</h2>
306+
<button onClick={() => reset()}>Try again</button>
307+
</body>
308+
</html>
309+
)
310+
}
311+
```

0 commit comments

Comments
 (0)