Next.js SSR Error Handling

#ProgrammingThu Sep 12 2024

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.

json-server

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.

Server Error Message

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.

Custom Error Message

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!