Error handling in Next.js App Router
Optimizing Error Handling in Next.js: A Guide to App Router Strategies
If you've been working with Next.js 14 and you've spotted the error.js
file in your directory, you might be wondering what it's all about. Well, you're in luck! Today, we're going to break down this file and see how it helps us out.
First of all, let's understand how the error file works. The error.js
file convention allows you to gracefully handle unexpected runtime errors in nested routes.
Automatically protect each part of your route along with its nested parts using a React Error Boundary.
Design error displays are customized for each section by organizing files in a hierarchy.
Contain errors within specific sections while keeping the rest of your app running smoothly.
Implement features to try fixing errors without reloading the entire page.
One of the most important things is that the error.js
file should be a client-side component. There is no option to make it a server-side component, as specified in the Next.js documentation. Let's look at the code of the error.js
file:
'use client' // Error components must be Client Components
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}
Here is an overview of how error.js
file work:
error.js
creates a React Error Boundary around a specific component or page.js. It uses the exported React component from error.js
as a backup. If an error occurs within this boundary, it's isolated, and the backup component is shown. Despite the error, layouts above the boundary stay functional, allowing for error recovery options within the backup component.
Recovering From Errors
If an error is temporary, retrying might fix it. Using the reset()
function in an error component prompts users to try again. When reset()
the Error Boundary attempts to re-render its contents. If successful, the fallback error component is replaced with the new result.
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
Nested Routes
Nested Routes in React involve rendering components in a structured hierarchy.
For instance, consider a scenario where two nested segments each contain layout.js
and error.js
files. This creates a hierarchy like this:
Nested Error Component Hierarchy
Errors propagate upwards to the nearest parent error boundary. This means that an
error.js
file will manage errors for all its nested child segments.Adjustments to the granularity of error UI are possible by positioning
error.js
files at various levels within the nested route folders.However, an error boundary defined in an
error.js
file won't handle errors from alayout.js
component in the same segment, as the error boundary is nested within that layout's component.
Handling Errors in Layouts
Error boundaries defined error.js
do not capture errors occurring in layout.js
or template.js
components within the same segment. This hierarchy is deliberate, ensuring that crucial UI elements shared among sibling routes, like navigation, remain accessible and usable during errors.
To manage errors within a particular layout or template, include an error.js
file in the parent segment of that layout.
For handling errors within the root layout or template, utilize a variant named global-error.js
.
When dealing with errors in root layouts:
The root app/error.js boundary doesn't handle errors from root
app/layout.js
orapp/template.js
components.For handling errors in these root components, utilize a variant named
app/global-error.js
, placed in the root app directory.Unlike
app/error.js
, global-error.js wraps the entire application, replacing the root layout with its fallback component when active. Thus,global-error.js
must include its own <html> and <body> tags.global-error.js
serves as broad error handling for the entire application, less frequently triggered due to the static nature of root components. However, it's still advisable to have a rooterror.js
file defining a fallback component within the root layout for consistent UI and branding.// app/global-error.tsx 'use client' export default function GlobalError({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return ( <html> <body> <h2>Something went wrong!</h2> <button onClick={() => reset()}>Try again</button> </body> </html> ) }
Handling Server Errors
When a Server Component encounters an error, Next.js forwards an Error object (with sensitive details removed in production) to the nearest error.js
file as the error prop.
To protect sensitive data, in production, only a generic message and a digest property are sent to the client. The digest is a hash of the error useful for matching with server-side logs.
In development, the Error object sent to the client includes the original error message for easier debugging.
Conclusion
In conclusion, error files, such as error.js or global-error.js, serve as crucial components in React applications for defining error UI boundaries within route segments. They effectively catch unexpected errors, whether originating from Server Components or Client Components and display fallback UI to maintain a smooth user experience. These files offer functionalities like resetting error boundaries and ensuring error handling consistency throughout the application. Additionally, the distinction between error.js and global-error.js provides flexibility in managing errors at different levels, from specific layouts to the root application. By adhering to best practices and leveraging tools like React Developer Tools, developers can optimize error-handling mechanisms to enhance the reliability and usability of their React applications.
We at CreoWis believe in sharing knowledge publicly to help the developer community grow. Let’s collaborate, ideate, and craft passion to deliver awe-inspiring product experiences to the world.
Let's connect:
This article is crafted by Syket Bhattachergee, a passionate developer at CreoWis. You can reach out to him on X/Twitter, LinkedIn, and follow his work on the GitHub.