Configuring Sentry for Next.js Apps

LR

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.

Discuss on TwitterEdit on GitHub
Spotify album cover

/uses/photos/newsletter