Error Handling in Next.js: Handling 404, Server-Side Errors, and JavaScript Runtime Errors
Adam C. |

Introduction

Error handling is an essential aspect of web development to ensure smooth user experiences and provide meaningful feedback when things go wrong. In this blog post, we'll explore how Next.js, a popular React framework for building server-rendered applications, facilitates error handling. Specifically, we'll cover handling 404 errors, server-side errors (500), and client-side errors caused by JavaScript runtime errors using the react-error-boundary package.

Photo by David Ballew on Unsplash

1. Handling 404 Errors

Next.js provides a straightforward way to handle 404 errors by creating a custom 404.js page. This page acts as a fallback when a route is not found. By creating this file within the pages directory, Next.js automatically recognizes it as the handler for 404 errors. You can customize the content and design of this page to suit your application's needs, providing users with helpful information or suggestions for navigation.

Example code for 404.js:

import React from 'react';

const NotFoundPage = () => {
  return (
    <div>
      <h1>Oops! Page not found.</h1>
      <p>The requested page does not exist.</p>
    </div>
  );
};

export default NotFoundPage;

To trigger 404 in getServerSideProps, we can use the code below:

    if (result.total === 0 || result.removed) {
      return {
        notFound: true,
      };
    }

2. Handling Server-Side Errors (500)

 To handle server-side errors, such as internal server errors (status code 500), Next.js allows you to create a custom 500.js page. This page serves as the error handler for server-side errors and is automatically triggered whenever an error occurs during server rendering or API requests. Similar to the 404 page, you have the flexibility to customize the content and appearance of the 500.js page.

Example code for 500.js:

import React from 'react';

const ServerErrorPage = () => {
  return (
    <div>
      <h1>Oops! Something went wrong.</h1>
      <p>We apologize for the inconvenience. Please try again later.</p>
    </div>
  );
};

export default ServerErrorPage;

To trigger 500 in getServerSideProps, we can use the code below:

try {
//....
}catch (error) {
    // console.log("ERROR", error);
    throw error;
}

3. Handling Client-Side Errors 

Next.js, being a React framework, allows us to leverage third-party libraries for handling client-side errors. One such library is react-error-boundary. It provides a declarative way to handle JavaScript runtime errors within React components. By wrapping components or specific parts of your application with an ErrorBoundary component, you can gracefully handle errors and display fallback UI.

Example code using react-error-boundary:

import React from 'react';
import { ErrorBoundary } from 'react-error-boundary';

const MyComponent = () => {
  // ...
};

const ErrorFallback = ({ error, resetErrorBoundary }) => {
  return (
    <div>
      <h1>Oops! Something went wrong.</h1>
      <p>An error occurred: {error.message}</p>
      <button onClick={resetErrorBoundary}>Retry</button>
    </div>
  );
};

const App = () => {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <MyComponent />
    </ErrorBoundary>
  );
};

export default App;

In this example, MyComponent is wrapped with an ErrorBoundary component, which defines the ErrorFallback component to render when an error occurs. The ErrorFallback component displays a user-friendly error message and provides an option to retry the operation.

Note

If we are unsure which particular component might throw a client-side error, we can add an ErrorBoundary component at the top level, which is typically done in the _app.js file. By wrapping the main component within the ErrorBoundary component, we can catch and handle any client-side errors that occur throughout the application. 

Example code for _app.js:

import React from "react";
import { ErrorBoundary } from "react-error-boundary";
import ErrorFallbackPage from "components/Common/ErrorFallbackPage";
import { dnxErrorHandler } from "../helpers/common";
class MyApp extends App {
  
  render() {
  	 const { Component, pageProps } = this.props;
     <ErrorBoundary
            FallbackComponent={ErrorFallbackPage}
            onError={dnxErrorHandler}
     >
         <Component {...pageProps} />
     </ErrorBoundary>
  }
}

In that case, we can create a fallback component (ErrorFallbackPage) that represents a full page, including the page layout, such as the header, footer, and other components. This ensures that the user can still see the complete page layout even when an error occurs, providing a consistent and seamless experience.

And to aid in debugging, we can also add an ErrorHandler function to log the error, providing valuable information for developers to investigate and address the issue effectively.

Example code for dnxErrorHandler.js:


const dnxErrorHandler = async (error, info) => {
  const data = {
    datetime: new Date().toLocaleString(),
    message:
      "COMPONENT: " +
      String(info.componentStack) +
      ", RENDERR ERROR: " +
      String(error.message),
    userAgent:
      typeof window !== "undefined" ? window.navigator.userAgent : "unknown",
  };

  try {
    await sendLog(data);
  } catch (error) {}
};

Conclusion

Next.js simplifies error handling by providing dedicated pages for handling 404 and server-side errors. Additionally, integrating react-error-boundary allows for robust handling of client-side JavaScript runtime errors