Next.js 15: Error Handling best practices for code and routes

Learn Error Handling in your Next.js 15 app. Discover how to handle errors in your code, manage server issues, and ensure smooth displays on routes

Bartłomiej Kozyra

Frontend Developer

2025-02-19

#Frontend

Time to read

10 mins

In this article

Introduction

error.tsx

global-error.tsx

not-found.tsx

ErrorBoundary and reset function

Handling Errors in Layouts

Handling Errors in Root Layouts

Handling Server-Side Errors

Summary

Share this article

Introduction

Error management is a fundamental aspect of software development. It involves anticipating, detecting, and addressing issues within a system. By implementing effective strategies for handling errors, developers can ensure their projects operate reliably even in unforeseen circumstances.

This can be done through throwing and catching exceptions, handling HTTP errors, and displaying appropriate error messages. There are four main categories of errors:

  • Logical errors - These are errors that arise when the program runs but does not return the expected results. This means that the logic applied in the code is incorrect, even though the syntax may be correct.
  • Generated errors - These are errors created based on specific conditions in the code, typically as a result of incorrect input data or unexpected states.
  • Compile-time errors - Errors that happen during the compilation of the code when the programmer uses incorrect syntax, missing elements, or incompatible data types. The compiler cannot process the code until these errors are fixed.
  • Runtime errors - These are errors that appear when the program is running. They can be caused by various issues, such as attempting to access non-existent resources, erroneous mathematical operations (e.g. division by zero), or attempts to access unallocated memory.

Since errors can have dire consequences, including system crashes, user logouts, or data loss, it is important to learn how we can safeguard our software. In this article, we will discuss error handling offered by Next.js 15. First, we would like to cover three key files: error.tsx, global-error.tsx, and not-found.tsx. We will explain their significance and when it is worthwhile to use them. Then we will analyze error handling in different parts of the project.

Request a free Next.js consultation

Facing technological challenges? Contact us for a free consultation in just 1 step!

What do you want to talk about?

How can we contact you?

You consent to being contacted by e-mail for the purpose of handling this request.

error.tsx

The convention of the error.tsx file allows for handling unexpected errors while using the application. It is useful for catching unexpected errors occurring on both the server and client side, as well as for displaying a fallback UI. An important point is that the error.tsx must be a client component, meaning it must include the directive use client. In production environments, errors returned from server components are scrubbed of specific details, which differs from behavior in the development environment, where the Error object retains complete error information.

Available parameters of the Error function:

  • error
    • error.message - contains the error message. For client component errors, this will be the original message, whereas for server components, it will be a general message that helps avoid leaking sensitive details.
    • error.digest - contains an automatically generated error digest that can be used to locate a specific entry in the server-side logs.
  • reset - a function for resetting the error boundary. When the reset function is called, it will attempt to re-render the contents of the error boundary. If successful, the fallback error component will be replaced with the result of the correct rendering. It is worth noting that error recovery can be useful in cases of temporary problem that may resolve with a retry.

ADDITIONAL INFORMATION:

It is a good practice to use React DevTools to test error states. We can also report errors to parent error boundaries. If you want errors to be passed to a parent error component, you can report them while rendering the error component. This enables better management and organization of error handling in the application.

example of error page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import Link from "next/link";
import React from "react";
import Image from "next/image";
import { BreakLine } from "@atoms/BreakLine";
import { Layout } from "@templates/Layout";
 
export default function Error(({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
    return (
       <Layout>
          <div className="flex items-center justify-between">
             <div>
                <h1 className="text-5xl font-bold">
                   Oops... <br />
                   Something went wrong
                </h1>
                <p className="my-12 max-w-[472px] text-lg">
                   Something’s not working right now. Try again later or head back to homepage.
                </p>
                <Button
                   onClick={() => reset()}
                   className="rounded-md bg-blue-600 px-10 py-2.5 text-base font-semibold uppercase tracking-widest text-white transition-all hover:bg-blue-500"
                >
                   Try again
                </Button>
             </div>
             <div className={"mt-34"}>
                <Image
                   src={"/images/error.png"}
                   alt={"alternative text for your image"}
                   width={380}
                   height={333}
                />
             </div>
             <BreakLine />
          </div>
       </Layout>
    );
}

global-error.tsx

This file is used for handling global errors that were not caught by error.tsx. It is important to remember that this file overrides the layout.tsx, meaning it must contain the <html> and <body> tags. This is a key difference compared to the error.tsx. The GlobalError function accepts the same parameters that were discussed in relation to the Error object.

ADDITIONAL INFORMATION:

In the development environment, when an error occurs, instead of the interface from the global-error.tsx, an error overlay appears on the screen. This overlay presents detailed information about the error, which greatly facilitates the debugging process and enables developers to quickly identify and resolve issues in the code.

not-found.tsx

When the server cannot find the requested resource, it returns a 404 error - "Not Found". This is a very common problem that occurs when an incorrect URL is provided, or when a user attempts to retrieve a file that has been removed from the server.

Next.js provides a friendly way to address this issue. In the /app directory, we create a file named not-found.tsx, in which we define a functional component called notFound(). This component is, by default, a server component, but we can convert it into an asynchronous function. This approach is especially useful when we want to fetch data and provide additional information to the client, for example, by suggesting similar products in an online store or supplying dynamic content based on the URL path.

THINGS TO REMEMBER

The not-found.tsx file should be defined globally by placing it in the app/not-found.tsx directory, allowing for consistent handling across the system. However, if you wish to manage errors in a specific manner for certain sections of the project, you can create dedicated error files. For instance, for a page displaying products, the not-found.tsx should be placed in the relevant directory, such as app/products/product/not-found.tsx. This provides flexibility in error management as well as consistent oversight across the entire project. The not-found.tsx components do not accept any props. Importantly, in addition to capturing expected errors using notFound(), the global app/not-found.tsx also handles all unmatched URLs across the entire system. This means that clients who visit a URL that is not supported will see the UI defined in the app/not-found.tsx.

example of not found page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import Link from "next/link";
import React from "react";
import Image from "next/image";
import { BreakLine } from "@atoms/BreakLine";
import { Layout } from "@templates/Layout";
import { PopularProducts } from "@organisms/PopularProducts";
import { getPopularProducts } from "@api/products";
 
export default async function NotFound() {
    const { products } = await getPopularProducts();
 
    return (
       <Layout>
          <div className="flex items-center justify-between">
             <div>
                <h1 className="text-5xl font-bold">
                   404 <br />
                   Product not found
                </h1>
                <p className="my-12 max-w-[472px] text-lg">
                   We couldn’t find the product you’re looking for. Head back to Homepage or try browsing another product.
                </p>
                <Link
                   href="/"
                   className="rounded-md bg-blue-600 px-10 py-2.5 text-base font-semibold uppercase tracking-widest text-white transition-all hover:bg-blue-500"
                >
                   Back to homepage
                </Link>
             </div>
             <Image
                src={"/images/not-found.png"}
                alt={"alternative text for your image"}
                width={380}
                height={333}
             />
          </div>
          <BreakLine />
          <PopularProducts products={products} />
       </Layout>
    );
}

ErrorBoundary and reset function

The ErrorBoundary component allows for capturing and handling errors that arise in child components. This helps to avoid the propagation of errors throughout the component tree. This component accepts a fallback parameter, which can be used to pass an error message in the form of a fallback component or text.

Advantages of using ErrorBoundary:

  • Isolation of errors: Errors in one component do not impact other parts of the application, ensuring stability throughout.
  • Enhanced user experience: Interactions can continue in functional areas of the interface, significantly improving overall usability.
  • Error logging: By enhancing ErrorBoundary to log error information to external services such as Sentry, developers can effectively monitor and diagnose problems without disrupting the UX.
  • Custom fallback UI: ErrorBoundary allows for the creation of a custom fallback UI that provides informative error messages along with options to refresh the page or navigate back to a safe state.

It is important to note that the causes of rendering errors can be temporary, so it is a good idea to implement a reset function that enables re-rendering of the component after an error has happened. This could be, for instance, a button that allows the component to be re-rendered after it has failed to render (see the example in error.tsx section).

Handling Errors in Layouts

If you already have knowledge about routing in your Next.js app, we can discuss issues related to error management. However, if you want to familiarize yourself with the basics first, we encourage you to read the article: Next.js 14: Exploring fundamental concepts, in which we mention routing in Next.

Error boundaries do not catch errors thrown in layout.tsx or template.tsx components of the same segment. To handle errors in layout.tsx using the error.tsx, it is necessary to move it to the parent segment. We can consider this topic in the context of a sample blog with a list of articles and individual articles. In this case, if we want to handle errors in layout.tsx for an article, the error.tsx must be placed in the blog directory, which is the parent directory.

An alternative to this approach is using a global error management file - global-error.tsx. In this case, we need to define our own HTML structure, which will include the <html> and <body> elements. This allows us to manage error management more flexibly within the same directory.

Handling Errors in Root Layouts

To effectively handle errors in the main components, you should use the global-error.tsx located in the app directory. Unlike the error.tsx, the global-error.tsx boundary encompasses the entire application, and its returned fallback component replaces the entire main layout. This component is rarely triggered since the boundaries of other components (if defined) usually catch most errors.

Even when using global-error.tsx, it is also advisable to establish an error.tsx, whose component will be rendered in the main layout. This way, the error component will be included in the global shared interfaces.

Handling Server-Side Errors

In Next.js, proper management of server errors is essential for creating a user-friendly application. When an error occurs in a server component, Next.js directs the Error object (stripped of sensitive error information in the production version) to the nearest error.tsx. It is important to remember that the message property provides a general description of the error, while digest provides an automatically generated error digest that can be used to match the appropriate error in the server logs.

To manage the state of Server Actions, including error handling, it is recommended to use the useActionState hook. This approach helps avoid using try/catch blocks for expected errors, which should be treated as return values rather than thrown exceptions. Using useActionState, we can pass an action and leverage the returned state to display an error message to the visitor.

app/actions.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
'use server';
import { redirect } from 'next/navigation';
 
interface NewsletterSubscribeState {
    ...
}
 
export async function subscribeToNewsletter(prevState: NewsletterSubscribeState, formData: FormData) {
    const email = formData.get('email');
    const res = await fetch('https://.../subscribe', {
        method: 'POST',
        body: JSON.stringify({ email }),
        headers: {
            'Content-Type': 'application/json',
        },
    });
 
    const json = await res.json();
 
    if (!res.ok) {
        return { message: 'Please enter a valid email address.' };
    }
 
    redirect('/thank-you');
}

app/ui/newsletterSubscription.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
'use client';
import { useActionState } from 'react';
import { subscribeToNewsletter } from '@/app/actions';
 
const initialState = { message: '' };
 
export function NewsletterSubscription() {
    const [state, formAction, pending] = useActionState(subscribeToNewsletter, initialState);
 
    return (
        <div className="flex items-center justify-center min-h-screen bg-gray-100">
            <div className="bg-white p-8 rounded-lg shadow-md w-96">
                <h2 className="text-2xl font-semibold mb-4">Subscribe our newsletter</h2>
                <form action={formAction}>
                    <div className="mb-4">
                        <label htmlFor="email" className="block text-sm font-medium text-gray-700">Adres email</label>
                        <input
                            type="email"
                            id="email"
                            name="email"
                            required
                            className="mt-1 block w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                        />
                    </div>
                    <p aria-live="polite" className="text-red-500 text-sm mb-2">{state?.message}</p>
                    <button
                        type="submit"
                        disabled={pending}
                        className={`w-full py-2 px-4 rounded-md text-white font-semibold ${pending ? 'bg-gray-400' : 'bg-blue-600 hover:bg-blue-700'} transition duration-200`}
                    >
                        {pending ? 'Loading...' : 'Subscribe'}
                    </button>
                </form>
            </div>
        </div>
    );
}

Summary

Error management in Next.js is quite simple. The framework offers three main files for addressing errors: error.tsx, global-error.tsx, and not-found.tsx, which assist developers in maintaining their applications in good condition. Each of these files has its unique functions and roles, and their proper use contributes to the quality and stability of the overall system. Thanks to these error management mechanisms, programmers can quickly respond to potential challenges and prevent failures.

It is important to remember that ensuring effective error management is crucial for maintaining a high-quality application. For this reason, when designing an error management strategy, it is essential to focus on providing users with clear communication. This means that visitors should receive understandable information about any problems that arise, as well as guidance on how to resolve them. Regardless of the programming language being used, developers should always pay attention to error management in order to ensure high-quality applications and positive visitor experiences.

Bartłomiej Kozyra

Frontend Developer

Share this post

Related posts

Frontend

Next.js 15: Dynamic routes and Static Site Generation (SSG)

Frontend

Next.js 15 has been officially released!

Frontend

How to create a mobile app from a web app in just a few hours

Frontend

Pigment CSS - new library from Material UI

Frontend

React 19 has been officially announced!

Frontend

Next.js 14: Exploring fundamental concepts

Frontend

Website Accessibility

Want to light up your ideas with us?

Kickstart your new project with us in just 1 step!

Prefer to call or write a traditional e-mail?

Dev and Deliver

sp. z o.o. sp. k.

Address

Józefitów 8

30-039 Cracow, Poland

VAT EU

PL9452214307

Regon

368739409

KRS

94552994