Configuring Sentry for Next.js Apps

Lee Robinson / April 23, 2019

5 min read––– views

Update (2020): This guide has been updated with the latest Sentry example.

For many teams using Next.js, setting up exception monitoring is a critical requirement before going to production. This guide will show you how to use Sentry to catch & report errors on both client and server-side.

Brief Next.js Overview

Next.js gives you a hybrid approach for rendering your application. You can choose on a per-page basis whether you want client-side rendering, server-side rendering, or static-site generation.

Next.js uses file system based routing. All files inside the /pages directory will be routed based on their filename (e.g. /pages/home.js routes to your-domain.com/home).

There are a few Next-specific files you should understand:

  • _app.js is used to initialize pages and renders on both the client and the server.
  • _error.js is rendered by Next.js while handling exceptions.
  • next.config.js allows you to override the default configuration.

Implementing Sentry

Note: You can view the completed example here.

  1. Set up an account with Sentry and retrieve your DSN from your project settings.
  2. Add the required environment variables.
.env.local
NEXT_PUBLIC_SENTRY_DSN=

# Only required to upload sourcemaps
SENTRY_ORG=
SENTRY_PROJECT=
SENTRY_AUTH_TOKEN=
  1. Add the required dependencies. Here's an example package.json. Ensure you have v0.0.4-canary.1 of @zeit/next-source-maps until a new version is published.
package.json
{
  "name": "with-sentry",
  "version": "1.0.0",
  "license": "ISC",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@sentry/browser": "^5.15.5",
    "@sentry/node": "^5.15.5",
    "@sentry/webpack-plugin": "^1.11.1",
    "@zeit/next-source-maps": "0.0.4-canary.1",
    "next": "latest",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  }
}
  1. Override _app.js to initialize Sentry. Note that it's only enabled when building for production.
pages/_app.js
import * as Sentry from '@sentry/node';

if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
  Sentry.init({
    enabled: process.env.NODE_ENV === 'production',
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN
  });
}

export default function App({ Component, pageProps, err }) {
  // Workaround for https://github.com/vercel/next.js/issues/8592
  return <Component {...pageProps} err={err} />;
}
  1. Override _error.js to handle uncaught exceptions.
pages/_error.js
import NextErrorComponent from 'next/error';
import * as Sentry from '@sentry/node';

const Error = ({ statusCode, hasGetInitialPropsRun, err }) => {
  if (!hasGetInitialPropsRun && err) {
    // getInitialProps is not called in case of
    // https://github.com/vercel/next.js/issues/8592. As a workaround, we pass
    // err via _app.js so it can be captured
    Sentry.captureException(err);
  }

  return <NextErrorComponent statusCode={statusCode} />;
};

Error.getInitialProps = async ({ res, err, asPath }) => {
  const errorInitialProps = await NextErrorComponent.getInitialProps({
    res,
    err
  });

  // Workaround for https://github.com/vercel/next.js/issues/8592, mark when
  // getInitialProps has run
  errorInitialProps.hasGetInitialPropsRun = true;

  // Running on the server, the response object (`res`) is available.
  //
  // Next.js will pass an err on the server if a page's data fetching methods
  // threw or returned a Promise that rejected
  //
  // Running on the client (browser), Next.js will provide an err if:
  //
  //  - a page's `getInitialProps` threw or returned a Promise that rejected
  //  - an exception was thrown somewhere in the React lifecycle (render,
  //    componentDidMount, etc) that was caught by Next.js's React Error
  //    Boundary. Read more about what types of exceptions are caught by Error
  //    Boundaries: https://reactjs.org/docs/error-boundaries.html

  if (res?.statusCode === 404) {
    // Opinionated: do not record an exception in Sentry for 404
    return { statusCode: 404 };
  }
  if (err) {
    Sentry.captureException(err);
    await Sentry.flush(2000);
    return errorInitialProps;
  }

  // If this point is reached, getInitialProps was called without any
  // information about what the error might be. This is unexpected and may
  // indicate a bug introduced in Next.js, so record it in Sentry
  Sentry.captureException(
    new Error(`_error.js getInitialProps missing data at path: ${asPath}`)
  );
  await Sentry.flush(2000);

  return errorInitialProps;
};

export default Error;
  1. Override next.config.js to produce source maps. This enables source maps in production for Sentry and swaps out @sentry/node for @sentry/browser when building the client side.
next.config.js
// Use the hidden-source-map option when you don't want the source maps to be
// publicly available on the servers, only to the error reporting
const withSourceMaps = require('@zeit/next-source-maps')();

// Use the SentryWebpack plugin to upload the source maps during build step
const SentryWebpackPlugin = require('@sentry/webpack-plugin');
const {
  NEXT_PUBLIC_SENTRY_DSN: SENTRY_DSN,
  SENTRY_ORG,
  SENTRY_PROJECT,
  SENTRY_AUTH_TOKEN,
  NODE_ENV,
  VERCEL_GITHUB_COMMIT_SHA,
  VERCEL_GITLAB_COMMIT_SHA,
  VERCEL_BITBUCKET_COMMIT_SHA
} = process.env;

const COMMIT_SHA =
  VERCEL_GITHUB_COMMIT_SHA ||
  VERCEL_GITLAB_COMMIT_SHA ||
  VERCEL_BITBUCKET_COMMIT_SHA;

process.env.SENTRY_DSN = SENTRY_DSN;

module.exports = withSourceMaps({
  webpack: (config, options) => {
    // In `pages/_app.js`, Sentry is imported from @sentry/browser. While
    // @sentry/node will run in a Node.js environment. @sentry/node will use
    // Node.js-only APIs to catch even more unhandled exceptions.
    //
    // This works well when Next.js is SSRing your page on a server with
    // Node.js, but it is not what we want when your client-side bundle is being
    // executed by a browser.
    //
    // Luckily, Next.js will call this webpack function twice, once for the
    // server and once for the client. Read more:
    // https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config
    //
    // So ask Webpack to replace @sentry/node imports with @sentry/browser when
    // building the browser's bundle
    if (!options.isServer) {
      config.resolve.alias['@sentry/node'] = '@sentry/browser';
    }

    // When all the Sentry configuration env variables are available/configured
    // The Sentry webpack plugin gets pushed to the webpack plugins to build
    // and upload the source maps to sentry.
    // This is an alternative to manually uploading the source maps
    // Note: This is disabled in development mode.
    if (
      SENTRY_DSN &&
      SENTRY_ORG &&
      SENTRY_PROJECT &&
      SENTRY_AUTH_TOKEN &&
      COMMIT_SHA &&
      NODE_ENV === 'production'
    ) {
      config.plugins.push(
        new SentryWebpackPlugin({
          include: '.next',
          ignore: ['node_modules'],
          urlPrefix: '~/_next',
          release: COMMIT_SHA
        })
      );
    }

    return config;
  }
});

Other Notes

  • You'll want to upload source maps for the most helpful errors. If you use Vercel, you can add the Sentry Integration. Otherwise, follow these steps.
  • Source maps and error tracking are not enabled in development mode.
  • Both @zeit/next-source-maps and @sentry/webpack-plugin are added to dependencies (rather than devDependencies) because if used with SSR, these plugins are used during production for generating the source-maps and sending them to Sentry.
Subscribe to the newsletter

Get emails from me about web development, tech, and early access to new articles.

- subscribers – 28 issues