Next.js SSR Error Handling
Handling errors in Next.js using getServerSideProps is essential for ensuring smooth server-side rendering (SSR) and a better user experience. In this post, we’ll explore how to manage SSR errors, the best way to handle SSR errors, and effective strategies for error handling.
Setting Up a Testing API with json-server
To test error handling, you’ll need an API. While you could use free public APIs like public-api, most of these have rate limits. Instead, I recommend creating your own local API using json-server. With json-server, you can quickly generate an API from a single JSON file.
Step 1: Install json-server
npm install json-server
Create a JSON file with your desired data and run the following command from the directory containing the file. By default, the API will run on port 3000.
Since I’m already using port 3000 for a web project, I changed the server port to 4000 by creating a json-server.json
file.
{
"port": 4000
}
I plan to test the stars example introduced in the Next.js documentation, so I created the following data in the db.json
file. After running the file, you can check the response at http://localhost:4000/response to confirm the data is set up correctly.
{
"response": {
"id": 0,
"stars": 1237
}
}
Step 2: Custom Error Handling
With port configuration and response confirmed, let's move on to our real goal—error handling. Custom Output settings can be configured in the server.js
file. Create this file and set it as follows:
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()
server.use(middlewares)
server.use(router)
server.listen(4000, () => {
console.log('JSON Server is running')
})
// Generate Custom Error
router.render = (req, res) => {
res.status(404).jsonp({
error: 'error message here',
})
}
You can run this custom server by executing:
node server.js
Alternatively, you can run the JSON file directly:
json-server db.json
Setting Up the Next.js Project
Once your API is up and running, you can create a new Next.js project using TypeScript:
npx create-next-app@latest --typescript
Now, in index.tsx
, we’ll fetch data from the local API, but no error handling is implemented at this stage.
export async function getServerSideProps() {
const res = await fetch('http://localhost:4000/response');
const json = await res.json();
return {
props: { stars: json.stars || 0 },
};
}
const Home = () => {
return (
<div>{/* ...your components here... */}</div>
);
};
export default Home;
Observing Errors
When the server’s API responds with a status of 404 or 500, you’ll see the error handled as a 500 error in SSR, along with an unhandled error modal in the browser.
Implementing Error Handling
How can we handle these errors? First, we need to pass not only the response value stars
when the status is 200 but also an error object. I included title
and statusCode
as values, which will only be set when the response fails.
export async function getServerSideProps() {
const res = await fetch('http://localhost:4000/response')
const error = res.ok
? false
: {title: 'An error occurred', statusCode: res.status}
const json = await res.json()
return {
props: {
error,
stars: json.stars || 0,
},
}
}
Now the task is simple. If there is an error in the props received by the component, return an Error component. If you want to use the Next.js-provided error page, you can import and use the Error component. Below is the code without the server-side fetching logic and type definition.
import Error from 'next/error';
const Home = ({ error, stars }: Props) => {
if (error) {
return <Error statusCode={error.statusCode} title={error.title} />;
}
return (
<div>{/* Your components here */}</div>
);
};
export default Home;
Creating Custom Error Pages
What if you want to show a custom error component instead of the one from Next.js? It's simple. Just create an _error
file in the pages directory. I'll use the example file from the official documentation.
function Error({ statusCode }) {
return (
<p>
{statusCode
? `An error ${statusCode} occurred on the server.`
: 'An error occurred on the client.'}
</p>
);
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Error;
By importing and running the custom error component instead of next/error
, you can see the custom message appearing correctly.
Note: Add a custom /_error
page along with a custom /404
page to avoid encountering an error.
Reference: Custom _error without /404
Should I Handle Errors for Each Page?
The short answer is no. The problem can be solved by handling errors only in the _app.tsx
file, which wraps all pageProps
. Simply process the error if it exists in the pageProps
.
First, go back to the error-handling pages and remove all error-handling logic. Then, delegate the error-handling logic to the _app.tsx
file. Upon refreshing, you'll see the error screen, indicating that error handling is working properly.
function MyApp({Component, pageProps}: AppProps) {
if (pageProps.error) {
return (
<Error
statusCode={pageProps.error.statusCode}
title={pageProps.error.message}
/>
)
}
return <Component {...pageProps} />
}
export default MyApp
Conclusion
So it really clears up a lot once you start looking at how to manage errors, especially when you need those custom error views. Whether you’re showing different views based on the error code or you’ve got a centralized way to handle things, it’s all about knowing the right approach.
And json-server is a handy tool when it comes to testing your error handling. It’s simple, flexible, and just makes the whole process smoother. Hopefully, this guide clears up some of the confusion if you’ve been facing similar issues!