In this article, we’ll learn how to handle errors in Next.js with the App Router and new error file conventions in Next.js.
Error handling is a key aspect of developing any web application, and in the past Next.js has helped developers with that experience through custom error pages like 404 and 500 pages.
However, these pages have their limitations within the Pages Router, such as limited support for specific UI integrations, outdated support for React error boundaries, and limited app functionality when errors occur.
But with the release of Next.js version 13.4, the new App Router has been marked stable for production. The App Router improves support and the developer experience for error handling and other essential parts of building web apps.
Key Takeaways
- Next.js version 13.4 introduces the App Router, which enhances the support and developer experience for error handling and other essential parts of building web apps.
- The error.tsx file in the app directory creates a React error boundary that prevents the app from crashing when an error occurs. It also acts as a fallback component that gets rendered when an error is thrown within the boundary.
- Custom exceptions can be created to abstract error messages across multiple routes in an application. For instance, a custom AuthRequiredError can be used to handle authentication errors across various routes.
- Errors can occur anywhere in a Next.js app. They bubble up to the nearest parent error boundary. For root layout or template errors, a global-error.tsx file should be used. If an error occurs in a server component or during data fetching, Next.js will forward the corresponding Error object to the nearest error.tsx boundary.
The Scenario and Setting Up
To facilitate understanding the new error-handling API, we’ll explore its implementation within a Next.js app for user authentication.
User authentication is prone to many errors, so learning how to handle errors in this context will stand you in good stead when you’re building other apps.
Before we start, get the code for the demo app we’ll be using in the article by cloning the repo linked here (on the main branch). Once you run the app, you should see the error pictured below.
In this demo app, the main page — which displays a table — can only be accessed by logged-in users, but some error (in this case man made, but it could legitimately happen) has occurred, and has led to the session
variable being assigned as null
.
Note: authentication won’t be implemented in the demo app for the sake of simplicity.
This of course leads to an error, and right now, the app completely crashes, because it doesn’t know how to handle the error!
Now we’ll learn how to handle that error to prevent our app from crashing, thereby significantly improving the UX of the app.
Creating the Error Page
To prevent the app from crashing, in the app/
directory, create an error.tsx
file. The creation of this file automatically creates a React error boundary that wraps the main page. Then, in the error.tsx
file, export the following function:
"use client";
export default function Error() {
return (
<div className="grid h-screen px-4 bg-white place-content-center">
<div className="text-center">
<h1 className="font-black text-gray-200 text-9xl">401</h1>
<p className="text-2xl font-bold tracking-tight text-gray-900 sm:text-4xl">
Unauthroized!
</p>
<p className="mt-4 text-gray-500">
You must be logged in to access the page
</p>
<button
type="button"
className="inline-block px-5 py-3 mt-6 text-sm font-medium text-white bg-indigo-600 rounded hover:bg-indigo-700 focus:outline-none focus:ring"
>
Try Again
</a>
</div>
</div>
);
}
Note: error components must be client components! Be sure to mark them as such.
The function exported will act as a fallback component. If an error is thrown within the boundary, the error will be caught and the fallback component will be rendered, which should appear as shown below.
Two props are passed to the error fallback component when an error happens — the error object itself, and a function to try to recover from the error (usually called reset
):
"use client";
type ErrorProps = {
error: Error;
reset: () => void;
};
export default function Error({ error, reset }: ErrorProps) {
// ...
}
We can now access the error message through the error
prop and display it on the screen as follows:
<p className="mt-4 text-gray-500">
{error.message || "You must be logged in to access the page"}
</p>
The reset function will try to rerender the original content surrounded by the error boundary when the function is called. If that’s successful, the fallback error component will be replaced with the contents from the rerender.
We can implement the reset function call in our button with an onClick
handler:
<button
type="button"
onClick={() => reset()}
className="inline-block px-5 py-3 mt-6 text-sm font-medium text-white bg-indigo-600 rounded hover:bg-indigo-700 focus:outline-none focus:ring cursor-pointer"
>
Try Again
</button>
And with that, we have successfully managed to handle our error!
Abstracting the Error Message
In an actual app with user authentication, there will likely be many routes that must be protected, which requires multiple instances of the same auth error message in case an auth error happens.
To abstract the error message (and not write it multiple times), we can easily create a custom exception relating to authentication.
To do this, create a directory called lib
and create a file in that directory called exceptions.ts
. In that file, we can create and export the custom auth error exception as follows:
export class AuthRequiredError extends Error {
constructor(message = "Auth is required to access this page") {
super(message);
this.name = "AuthRequiredError";
}
}
We can now throw this new custom AuthRequiredError
on the main page instead of the regular Error
:
export default function Home() {
if (!session) throw new AuthRequiredError();
// ...
}
The error will give us either the default message passed in the constructor or a more specific error that we may need to pass later on.
More About Error Handling
Let’s end with some extra things to note about errors in layouts and server errors.
Errors in Layouts
Eerrors can happen anywhere in an app (not just page.tsx
files), and the file routing system Next.js uses influences how error.tsx
boundaries work across nested routes and layouts.
Errors bubble up to the nearest parent error boundary, which can be seen in the diagram below.
This error-bubbling nature means that an error.tsx
boundary won’t catch an error in a layout file on the same segment, because the error boundary wraps the layout file.
If an error occurs in the root layout or template, use a global-error.tsx
file, as pictured below.
The global-error.tsx
boundary wraps the entire app, so be sure to add your own unique <html>
and <body>
tags when using this file.
This error boundary catches any errors that weren’t caught by other nested error.tsx
boundaries, and as such it won’t be activated often.
Server Errors
In case an error occurs in a server component or while data fetching, Next.js will forward the corresponding Error
object to the nearest error.tsx
boundary.
Conclusion and Next Steps
Although many developers view implementing error handling as a hassle, it’s a vital part of an app, and successfully implementing error handling will significantly improve the app’s UX.
Next.js makes it incredibly simple to do this with the help of the App Router and the error.tsx
file convention.
You can consult the Next.js docs on error handling to learn more about error handling, and you can view the finished code from this article on GitHub.
FAQs on Mastering Error Handling in Next.js with the App Router
What is the role of error handling in Next.js?
Error handling is a crucial aspect of any application development, and Next.js is no exception. It helps in identifying and managing potential problems that may occur during the execution of an application. In Next.js, error handling is primarily managed through the Error component and the getInitialProps lifecycle method. The Error component is used to display error messages to the user, while getInitialProps is used to fetch data and handle errors during server-side rendering. Proper error handling can greatly improve the user experience by providing informative feedback when something goes wrong.
How does the Error component work in Next.js?
The Error component in Next.js is a built-in component that is used to handle and display errors. It receives a statusCode prop which determines what error message to display. For instance, if the statusCode is 404, it means the page was not found. If there’s no statusCode, it means an error occurred on the client-side. You can also customize the Error component to display custom error messages or styles.
How does getInitialProps work in error handling?
The getInitialProps method in Next.js is a special lifecycle method that allows you to fetch data and handle errors during server-side rendering. It is executed before the page is rendered and the returned object is used as props for the page component. If an error occurs during this process, you can catch it and return an error object. This error object can then be used in the Error component to display an appropriate error message to the user.
How can I customize the Error component in Next.js?
Customizing the Error component in Next.js is quite straightforward. You can create a new file named _error.js in the pages directory. This file should export a React component that receives a statusCode prop. You can then use this prop to determine what error message or style to display. This allows you to create a more personalized and user-friendly error handling system.
What is the difference between client-side and server-side errors in Next.js?
Client-side errors in Next.js are errors that occur in the browser, after the page has been rendered. These are typically caused by issues in the client-side JavaScript code. On the other hand, server-side errors occur during the server-side rendering process, before the page is sent to the browser. These could be caused by issues like network errors or problems with the server-side code. The Error component in Next.js can handle both types of errors.
How can I handle 404 errors in Next.js?
In Next.js, 404 errors are handled by the Error component. If a page is not found, Next.js will automatically pass a statusCode of 404 to the Error component. You can customize the Error component to display a custom 404 error page. Alternatively, you can create a custom 404 page by creating a file named 404.js in the pages directory.
How can I handle network errors in Next.js?
Network errors in Next.js can be handled using the getInitialProps method. If a network request fails during server-side rendering, you can catch the error and return an error object. This object can then be used in the Error component to display an appropriate error message. It’s important to handle network errors properly to prevent the application from crashing and to provide a good user experience.
Can I use try/catch blocks for error handling in Next.js?
Yes, you can use try/catch blocks for error handling in Next.js. This is particularly useful in the getInitialProps method, where you can wrap your data fetching code in a try/catch block. If an error occurs, you can catch it and return an error object. This object can then be used in the Error component to display an appropriate error message.
How can I test error handling in Next.js?
Testing error handling in Next.js can be done using various testing libraries like Jest and React Testing Library. You can simulate errors and check if the correct error message is displayed. You can also test if the application behaves correctly when an error occurs, for instance, by checking if it redirects to an error page.
Can I use third-party libraries for error handling in Next.js?
Yes, you can use third-party libraries for error handling in Next.js. There are many libraries available that provide advanced error handling features, such as error logging, error tracking, and more. However, it’s important to note that these libraries should be used in addition to, not instead of, the built-in error handling features of Next.js. The built-in features provide a solid foundation for error handling, while third-party libraries can be used to add more advanced functionality.
Rayan Kazi is a full-stack web developer and technical writer acquainted with the latest tech on the market. His strong drive allows him to solve complex problems with robust solutions. You can find him on Twitter or at his website.