Chris.luChris.lu header image, displaying an 80s style landscape and sunset

Error handling and logging

As we saw earlier, each route segment is a directory into we can put different files. We already created the page(.tsx|.jsx|.mdx) file, then Next.js 15 created a default layout file for us, the third file we will now create is an error page

How this works is that Next.js will automatically wrap the children of your page with a React Error Boundary, meaning that when an error gets thrown in a page, then the error boundary will contain it and then use the error file that is the closest (either an error file that is in the same directory as the page itself or a parent directory), that error page can do different things with the error, obviously it can display an error message to the user but you can also use it to do other things like save the error in your database or send a request to your logging service

Let's create our first error file inside of our /app directory, and let's use the example from the Next.js documentation like so:

app/error.tsx
'use client' // Error components must be Client Components
 
import { useEffect } from 'react'
 
interface ErrorBoundaryProps {
    error: Error & { digest?: string }
    reset: () => void
}
 
export default function Error({
    error,
    reset,
}: ErrorBoundaryProps) {
 
    useEffect(() => {
        // simulate logging the error
        console.error(error)
    }, [error])
 
    return (
        <>
            <h1>Sorry, something went wrong ๐Ÿ˜ž</h1>
            <button
                onClick={() => {
                    // attempt to recover by trying to re-render the segment
                    reset()
                }}
            >
                Try again
            </button>
        </>
    )
}

Lines 5 to 8: we create a Typescript interface for the props our error boundary

Lines 10 to 13: a Next.js error boundary will give you two useful things, the error itself and a function that can rerender the segment in which the error occurred

Lines 23 to 28 there is the second feature of this component, a button which will trigger the reset function we got from the component props (line 10), this function provided by Next.js will attempt to rerender the segment that triggered an error, this is helpful if the error was caused by something that occurs sporadically and might allow the user to continue

As you can see, the Next.js documentation example uses a useEffect() function (lines 13 to 16) and inside of it we use a console.log to print an error in the console of the browser, but what happens if the error is getting triggered on a user's computer, then we won't know about it. This is why in the next chapter, we will use a third-party service called Sentry.io to do the logging for us (of course if you prefer you can also develop and run your logging service instead)

Using Sentry.io to log errors

In this chapter, we will use Sentry.io (which has a free plan for side projects) to add error logging to the Next.js error file we just created

We will use the Sentry.io Wizard tool to install the Sentry.io SDK for Next.js or you can follow their manual setup tutorial (if you prefer a manual installation).

Sentry.io account & first project

First, you need to have or create an account on Sentry.io (if you need help with that step, check out my chapter Create a Sentry account (sign up) in the Sentry.io post)

Now we need to create a new project on Sentry.io (if you need help with that step, check out my chapter Create a Sentry.io project in the Sentry.io post)

Tip

Before using the wizard, I recommend committing your latest changes (if you haven't already) and doing a last sync before launching the sentry wizard, as the Sentry Wizard will add and edit some existing files. This way you will be able to see (using a git diff after the wizard is done) which new files the Sentry Wizard has added, and also see what got changed in existing files like the next.config.(mjs|ts) file

Next.js 15.x canary (optional)

If you don't use the latest stable version of Next.js 15.x but prefer to use a canary version, then attempting to add the latest version of Sentry for Next.js will likely result in a dependency error:

npm error ERESOLVE unable to resolve dependency tree
โ”‚  npm error
โ”‚  npm error While resolving: nextjs15_sentry_wizard_reproduction@0.1.0
โ”‚  npm error Found: next@15.x.x-canary.x

You can bypass this error by using the overrides in your package.json, here is an example of what your package would look like with an overrides for Next.js 15 canary:

package.json
{
    "name": "MY_PROJECT",
    "version": "0.0.1",
    "scripts": {
        "dev": "next dev",
        "dev-turbo": "next dev --turbopack",
        "build": "next build",
        "start": "next start",
        "lint": "next lint"
    },
    "dependencies": {
        "next": "canary",
        "react": "canary",
        "react-dom": "canary"
    },
    "devDependencies": {
        "@types/node": "22.10.2",
        "@types/react": "19.0.2",
        "@types/react-dom": "19.0.2",
        "typescript-eslint": "8.18.1"
    },
    "overrides": {
        "next": "canary"
    }
}

Lines 22 to 24 we add an overrides that will tell npm that it should use this Next.js version instead of the one it finds in the peer dependencies for Sentry and potentially other packages as well

Note

In the package.json I used the canary tag, if you want to use an exact version you can use the npmjs next versions page or you have a look inside of their next package.json.

When you update your exact canary version, make sure that you update both Next.js versions, the one in your dependencies and the one you use in the overrides

Sentry Installation Wizard

npx @sentry/wizard@latest -i nextjs

Or, if you prefer to use the wizard without sending telemetry data to sentry.io (usage statics and crash reports), then add the --disable-telemetry option to the command, like so:

npx @sentry/wizard@latest -i nextjs --disable-telemetry

Wizard Q&A

First, you will get asked if you accept installing the sentry wizard npm package. Press y to accept and press Enter to move on

After the installation, the wizard will automatically get launched, and it will start asking you questions about your setup preferences:

Are you using Sentry SaaS or self-hosted Sentry? You probably want to choose Sentry SaaS (sentry.io) like I did (but if you are a company and need a custom solution, then you might want to look at the hosted version), then press Enter.

Do you already have an account? chose Yes (if you did follow the previous chapter or already had an account before), then press Enter (or choose No if you have no account yet and follow the account creation process)

Then, the sentry wizard will ask you to log in, which will open the sentry login page in your default browser. Log into your account, then go back to your terminal.

Select your Sentry project, choose your Sentry Project from the list (when using the wizard, Sentry will have automatically created a project for the SDK you chose earlier for you; if, however, you don't see a Project listed here, you can check out the Create a Sentry.io project chapter to create a project first)

Now, Sentry will install the latest Sentry SDK for Next.js.

Do you want to route Sentry requests in the browser through your NextJS server to avoid ad blockers? Sentry wants to know if it should route its requests through your Next.js server. By doing so, Sentry attempts to bypass the block lists of adblocker addons that are installed in some browsers. This means Sentry will first send the client-side requests to a URL on your server, and then your server will forward the request to the Sentry API. I personally chose Yes as I want to increase the chance of getting bug reports but feel free to answer No (if for example, you don't want to have the extra traffic, on your server backend, that this redirect will cause)

Do you want to enable React component annotations to make breadcrumbs and session replays more readable? Next Sentry is asking if we want to use the feature called React component annotations which attempts to use component names in reports instead of more cryptic selectors, I think this is a nice feature, so I selected Yes, if you already use Sentry.io and don't want to change how bug reports work, then leave it on No, you can always turn it on/off via the configuration later if you want

Warning

I turned React component annotations on and then noticed that my react-three-fiber animation had stopped working, this is because React component annotations adds data attributes to components which React Three Fiber does not like, and which then creates bugs which print the following in your console:

TypeError: Cannot read properties of undefined (reading 'sentry')

So if you plan on using React Three Fiber then you should answer to this question with NO, to learn more about this problem and how to disable React component annotations manually in the configuration have a look at the Sentry React Component Annotation(s) can be problematic chapter

Do you want to create an example page chose YES (we will later use it to test the Sentry setup, and then we will delete it)

Are you using a CI/CD tool to build and deploy your application? chose YES (if you are using Vercel, GitHub actions, or any other CI/CD deployment tool); If you do NOT use one, choose NO)

The Sentry.io Wizard will give you a SENTRY_AUTH_TOKEN string if you choose yes. If you use a CI/CD for your deployments, copy the token, and save it in a secure location, you will need this token later if, for example, you set up a custom GitHub action. You will want to add that token environment variable to your GitHub secrets. If you use another CI/CD service, check out their documentation to learn how to use that token to upload source maps to Sentry automatically. If using Vercel, you can use the Sentry integration for Vercel, which will set the Vercel environment variables for you, or if you prefer you can add the token manually to your environment variables using the Vercel environment variables interface.

CI/CD tools can authenticate themselves to Sentry.io using the SENTRY_AUTH_TOKEN environment variable and then use the Sentry.io API to automatically upload the source maps of your build to Sentry.io. Later, if there is a bug report on Sentry.io, it will be able to use the source maps instead of the minified build files to show you where the error occurred.

Did you configure CI as shown above? chose YES

That was the last question:

Successfully installed the Sentry Next.js SDK!

After answering all questions, the Sentry SDK will edit your next.config.mjs to add the withSentryConfig Sentry configuration, and it will have added several sentry.*.config files (to the root of your project) that contain environment-specific configurations, it will also create some other files depending on what answers you gave, like a page.tsx that we can now use to test the setup

Note

If you use Vercel for your deployments, then you don't need to set the SENTRY_AUTH_TOKEN yourself; you can use the Sentry integration for Vercel, which will set the Vercel environment variables for you, I recommend you do that now, as all you need to do is click on the Add integration button, and then continue with the tutorial

Update Sentry SDK version (to use alpha or beta) (optional)

If you use the wizard it will always use the latest version of Sentry for Next.js

If you are feeling adventurous, you could try out the latest alpha or beta versions, to know what versions are currently available I recommend having a look at the Sentry for Next.js version page and check out what the latest versions are

Sentry for Next.js has a next branch that is similar to a canary in Next.js, if you want to install that version (instead of the latest that the wizard installed), then use the following command:

npm i @sentry/nextjs@next --save-exact

This will replace the current version of Sentry for Next.js with a more up to date but also more experimental version

Warning

be careful when you chose what version you install as there is not always an alpha or beta available, sometimes the newest beta or alpha that is tagged with next (on npmjs) will actually be older than the version tagged as latest

Sentry configuration

Sentry.io can be customized a lot and has several places to change the default configuration.

The main part of the Sentry for Next.js configuration is in your next.config.ts file (which is in the root of your project):

next.config.ts
import { withSentryConfig } from '@sentry/nextjs'
import type { NextConfig } from 'next'
import { PHASE_DEVELOPMENT_SERVER } from 'next/constants'
 
const nextConfig = (phase: string) => {
 
    if (phase === PHASE_DEVELOPMENT_SERVER) {
        console.log('happy development session ;)')
    }
 
    const nextConfigOptions: NextConfig = {
        reactStrictMode: true,
        poweredByHeader: false,
        experimental: {
            // experimental typescript "statically typed links"
            // https://nextjs.org/docs/app/api-reference/next-config-js/typedRoutes
            typedRoutes: true,
        }
    }
 
    return nextConfigOptions
 
}
 
export default withSentryConfig(
    nextConfig,
    {
        // For all available options, see:
        // https://github.com/getsentry/sentry-webpack-plugin#options
 
        org: "MY_ORGANIZATION_NAME",
        project: "MY_PROJECT_NAME",
 
        // Only print logs for uploading source maps in CI
        silent: !process.env.CI,
 
        // For all available options, see:
        // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
 
        // Upload a larger set of source maps for prettier stack traces (increases build time)
        widenClientFileUpload: true,
 
        // Automatically annotate React components to show their full name in breadcrumbs and session replay
        reactComponentAnnotation: {
            enabled: true,
        },
 
        // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
        // This can increase your server load as well as your hosting bill.
        // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
        // side errors will fail.
        tunnelRoute: "/monitoring",
 
        // Hides source maps from generated client bundles
        hideSourceMaps: true,
 
        // Automatically tree-shake Sentry logger statements to reduce bundle size
        disableLogger: true,
 
        // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
        // See the following for more information:
        // https://docs.sentry.io/product/crons/
        // https://vercel.com/docs/cron-jobs
        automaticVercelMonitors: true,
    }
)

Lines 25 to 66: the Sentry wizard has updated your next.config.ts based on the answers you gave to the wizard questions (I formatted mine (in VSCode, you can right click and then select Format Document)

Now is a good time to commit the updated next.config.ts (and other changes the wizard did)

Note

By default, the options that the wizard set for us are good enough. As soon as you have the time, I recommend checking out the Extend your Next.js Configuration Sentry.io documentation, which explains what each option does

Sentry React Component Annotation(s) can be problematic

To enable Sentry reactComponentAnnotation configuration option is usually a good idea as it makes reports more readable by using component names instead of long selectors

To make this feature happen Sentry needs to add a data attributes to components, this does usually not pose a problem except sometimes the Sentry Annotations on third party components will cause an error in those third party tools

I have a more detailed explanation about annotations and the problems that can occur in my Sentry Post

The solution to these problems is to disable the feature and wait for the Sentry team to fine tune the feature (which is quite young) and fix annotations bugs (I have linked to some tickets in my Sentry post, you may want to subscribe to them if you want to keep track of fixes)

Sentry does not (yet) support Turbopack

If you try to use Turbopack with Sentry you will get the following error:

[@sentry/nextjs] WARNING: You are using the Sentry SDK with `next dev --turbo`. The Sentry SDK doesn't yet fully support Turbopack. The SDK will not be loaded in the browser, and serverside instrumentation will be inaccurate or incomplete. Production builds without `--turbo` will still fully work. If 
you are just trying out Sentry or attempting to configure the SDK, we recommend temporarily removing the `--turbo` flag while you are developing locally. Follow this issue for progress on Sentry + Turbopack: https://github.com/getsentry/sentry-javascript/issues/8105. (You can suppress this warning by 
setting SENTRY_SUPPRESS_TURBOPACK_WARNING=1 as environment variable)

As of now (dec. 2024) there is no workaround available for this, you need to make a choice and either not use Turbopack or use Turbopack but then Sentry will not get loaded in development (production builds are not impacted as Next.js does NOT (yet) use Turbopack for production builds)

In the meantime if you are interested in seeing how the Sentry JS SDK support for Turbopack progresses, then I recommend subscribing to their "Turbopack Support" issue #8105

Note

I assume the first Sentry SDK release that will be fully compatible with Turbopack will be v9 (this is an assumption)!? But before that happens there is a quite long list of Sentry javascript SDK v9 tasks, the Turbopack changes are related to withSentryConfig

The instrumentation file

Next.js has added support for a new instrumentation which has the goal to improve the integration of monitoring and logging tools into Next.js

Sentry wizard will have created such a instrumentation.ts file for us, with the following content:

instrumentation.ts
export async function register() {
    if (process.env.NEXT_RUNTIME === 'nodejs') {
        await import('./sentry.server.config')
    }
 
    if (process.env.NEXT_RUNTIME === 'edge') {
        await import('./sentry.edge.config')
    }
}

Sentry Client Configuration file

Another file that got added to the root of our project is sentry.client.config.ts, which is used to configure Sentry.io for client components. Check out the Next.js SDK Configuration Options documentation for more details about each option. We will slightly modified the Sentry configuration to this:

sentry.client.config.ts
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
 
import * as Sentry from '@sentry/nextjs'
 
let replaysOnErrorSampleRate = 0
let tracesSampleRate = 0.1
 
if (process.env.NODE_ENV === 'production') {
    replaysOnErrorSampleRate = 1
}
 
if (process.env.NODE_ENV === 'development') {
    tracesSampleRate = 0
}
 
Sentry.init({
    dsn: 'YOUR_SENTRY_DSN_URL',
 
    // Adjust this value in production, or use tracesSampler for greater control
    tracesSampleRate: tracesSampleRate,
 
    // Setting this option to true will print useful information to the console while setting up Sentry.
    debug: false,
 
    replaysOnErrorSampleRate: replaysOnErrorSampleRate,
 
    // on free plan lower (as limited to 50 per month)
    // if you have a paid plan set it higher
    replaysSessionSampleRate: 0,
 
    // You can remove this option if you're not planning to use the Sentry Session Replay feature:
    integrations: [
        Sentry.replayIntegration({
            // Additional Replay configuration goes in here, for example:
            maskAllText: true,
            blockAllMedia: true,
        }),
    ],
 
    environment: process.env.NODE_ENV ? process.env.NODE_ENV : '',
})

Lines 7 to 8: we added two variables for the replaysOnErrorSampleRate we set it to zero by default to not produce replays when not in production and we set the tracesSampleRate to 0.1, meaning we want to limit the amount of traces that get recorded (and sent to sentry) to 10%

Lines 10 to 12: we added a check to verify if the current environment is production and if it is we tell Sentry to always make a replay if there is an error (be careful with this option, in the free plan you only have 50 replays per month, which is why I only turn it on in production, also in development it is usually the developer that triggers the error so there is not really a need for a replay)

Lines 14 to 16: we disable the traces in development (this is to ensure no performance metrics are getting calculated when the app is running on a local computer, to check local performance it is preferred to use the developer tools), you may want a lower or higher value depending on what plan you are on and then check if you reach your limits or not and then adjust over time

Lines 22 and 27: we replace the default sentry values with our replaysOnErrorSampleRate and tracesSampleRate variables

Line 31: we set the replaysSessionSampleRate to zero (related to the changes we did line 10 to 12)

Line 42: we pass the environment to Sentry, meaning Sentry will know if the environment is preview or production (that value is based on the Vercel environment on which Next.js got deployed)

Warning

If you copy paste the sentry.client.config.ts above into your project make sure you update the YOUR_SENTRY_DSN_URL placeholder with your own Sentry DSN

sentry.edge.config.ts are the options for when Next.js uses the Vercel Edge Network. I kept that file, but feel free to adjust any values to fit your use case.

sentry.server.config.ts is again similar to the previous two, just this one is specifically for Next.js server-side options. I also kept this file as is

There is, however, one option in the server configuration that is commented out (by default) that you might want to consider. The option lets you use the Spotlight js package by Sentry. If you're going to use it, I recommend checking out the documentation to install and setup Spotlight in a Next.js project

Sentry Next.js example page

Next, as suggested by the wizard at the end of the installation process, it is recommended to start the development server using the npm run dev command, and then we visit the Sentry example page in our browser at http://localhost:3000/sentry-example-page

On that page, hit the Throw Error! button and then click on the link just below to visit your Sentry projects issues page

Now wait for the backend and frontend errors to appear (this can take a few minutes, which is a good time to refresh your cup of coffee โ˜• (or whatever beverage you prefer))

As soon as the two errors appear, feel free to click on them and have a look at what error logging on Sentry.io looks like

Before we commit/sync all the changes Sentry.io did in our project, delete the app\sentry-example-page\ folder, including the page.jsx example page, and then also delete the app\api\sentry-example-api\ folder including the route.js API example route file that Sentry.io created to test the error logging, you will not need them anymore.

Error page with Sentry error logging

Now that Sentry.io is set up, we can modify the error file we created earlier, like so:

app/error.tsx
'use client' // Error components must be Client Components
 
import * as Sentry from '@sentry/nextjs'
import { useEffect } from 'react'
 
interface ErrorBoundaryProps {
    error: Error & { digest?: string }
    reset: () => void
}
 
export default function Error({
    error,
    reset,
}: ErrorBoundaryProps) {
 
    useEffect(() => {
        // log the error to Sentry.io
        Sentry.captureException(error)
    }, [error])
 
    return (
        <>
            <h1>Sorry, something went wrong ๐Ÿ˜ž</h1>
            <button
                onClick={() => {
                    // attempt to recover by trying to re-render the segment
                    reset()
                }}
            >
                Try again
            </button>
        </>
    )
}

Line 3: we import the Sentry SDK

Lines 17 to 18: we replace our console.error (inside of the useEffect()) with the Sentry.io captureException logging function

Handling global errors

The Sentry.io wizard we just used has created a Next.js app/global-error.jsx file for us

The Next.js documentation explains well why this file is essential:

The root app/error boundary does not catch errors thrown in the root app/layout or app/template component.

To specifically handle errors in these root components, use a variation of error.js called global-error.js located in the root /app directory.

global-error is the least granular error UI and can be considered "catch-all" error handling for the whole application.

Warning

same as for the warnings above, if you used the current wizard with Next.js and a typescript configuration file then the wizard will have created a app/global-error.jsx file, if this is your case then delete the app/global-error.jsx and create a new app/global-error.tsx file, then insert the code as shown below

We will update the global-error file Sentry just created, and add a slightly different UI using the same reset button we added to the regular error page instead of using the Next.js built in NextError component, the final version looks like this:

app/global-error.tsx
'use client' // Error components must be Client Components
 
import * as Sentry from '@sentry/nextjs'
import { useEffect } from 'react'
 
interface GlobalErrorProps {
    error: Error & { digest?: string }
    reset: () => void
}
 
export default function GlobalError({
    error,
    reset,
}: GlobalErrorProps) {
 
    useEffect(() => {
        // log the error to Sentry.io
        Sentry.captureException(error)
    }, [error])
 
    return (
        <html>
            <body>
                <h1>Sorry, something went wrong ๐Ÿ˜ž</h1>
                <button
                    onClick={() => {
                        // attempt to recover by trying to re-render the segment
                        reset()
                    }}
                >
                    Try again
                </button>
            </body>
        </html>
    )
}

Line 3: we import the Sentry SDK

Lines 17 to 18: we replace our console.error (inside of the useEffect()) with the Sentry.io captureException logging function

Tip

The global error handling file will handle root layout errors and act as a catch-all for app errors

Time to save, commit, and sync

Congratulations ๐ŸŽ‰ you now have error handling and logging for pages as well as global error handling in your project

If you liked this post, please consider buying me a coffee โ˜• or sponsor โค๏ธ me on GitHub, as it will help me create more content and keep it free for everyone