diff --git a/examples/with-sentry/.env.local.example b/examples/with-sentry/.env.local.example deleted file mode 100644 index 24d834ca25973..0000000000000 --- a/examples/with-sentry/.env.local.example +++ /dev/null @@ -1,25 +0,0 @@ -NEXT_PUBLIC_SENTRY_DSN= - -# Only required to upload sourcemaps -# SENTRY_ORG= -# SENTRY_PROJECT= -# SENTRY_AUTH_TOKEN= -# -# Only required if sentry for organization -# Ex: https://sentry.ORG.com/ -# SENTRY_URL= -# -# For sourcemaps to work with server-side exceptions, the file path of the -# uploaded .map file needs to match the file paths in Error.stack. In Node.js, -# Error.stack file paths are absolute. Since the .map files we upload to Sentry -# have relative paths (~/_next), Error.stack needs to be rewritten to also use -# relative paths, which is handled in Sentry.init via Sentry's RewriteFrames -# integration. -# -# Normally, the root directory could be detected with __dirname, but __dirname -# isn't yet supported in Vercel serverless functions: -# https://github.com/vercel/next.js/issues/8251 -# -# To work around this issue, provide the root directory containing Next.js's -# build output here. In the Vercel environment, this is /var/task/. -# NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR= diff --git a/examples/with-sentry/README.md b/examples/with-sentry/README.md index f38bd64b1e3cb..a08bb22be75c0 100644 --- a/examples/with-sentry/README.md +++ b/examples/with-sentry/README.md @@ -1,18 +1,20 @@ # Sentry -This is an example showing how to use [Sentry](https://sentry.io) to catch & report errors on both client + server side. +This is an example showing how to use [Sentry](https://sentry.io) to catch & report errors on both client + server side, using the [official Sentry SDK for Next.js](https://docs.sentry.io/platforms/javascript/guides/nextjs/). -- `_app.js` renders on both the server and client. It initializes Sentry to catch any unhandled exceptions +- `_app.js` renders on both the server and client - `_error.js` is rendered by Next.js while handling certain types of exceptions for you. It is overridden so those exceptions can be passed along to Sentry -- Each API route also initializes Sentry, so it can work independently in the "serverless" build config -- `next.config.js` enables source maps in production, uploads them to a new Sentry release, and swaps out `@sentry/node` for `@sentry/browser` when building the client bundle +- Each API route is handled with `withSentry` +- `next.config.js` automatically configures the app to use Sentry through `withSentry` ## Deploy your own -Once you have access to your [Sentry DSN](#step-1-enable-error-tracking), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): +Once you have access to your [Sentry DSN](https://docs.sentry.io/product/sentry-basics/dsn-explainer/#where-to-find-your-dsn), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-sentry&project-name=with-sentry&repository-name=with-sentry&env=NEXT_PUBLIC_SENTRY_DSN&envDescription=DSN%20Key%20required%20by%20Sentry&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-sentry%23step-1-enable-error-tracking) +Check out [Sentry’s Vercel Integration](#sentry-integration). + ## How To Use Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: @@ -25,61 +27,8 @@ yarn create next-app --example with-sentry with-sentry-app ## Configuration -### Step 1. Enable error tracking - -Copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git): - -```bash -cp .env.local.example .env.local -``` - -Next, Copy your Sentry DSN. You can get it from the settings of your project in **Client Keys (DSN)**. Then, copy the string labeled **DSN** and set it as the value for `NEXT_PUBLIC_SENTRY_DSN` inside `.env.local` - -> **Note:** Error tracking is disabled in development mode using the `NODE_ENV` environment variable. To change this behavior, remove the `enabled` property from the `Sentry.init()` call inside your `utils/sentry.js` file. - -### Step 2. Run Next.js in development mode - -```bash -npm install -npm run dev - -# or - -yarn install -yarn dev -``` - -Your app should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). - -### Step 3. Automatic sourcemap upload (optional) - -#### Using Vercel - -You will need to install and configure the [Sentry Vercel integration](https://docs.sentry.io/workflow/integrations/vercel). After you've completed the project linking step, all the needed environment variables will be set in your Vercel project, with the exception of `NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR`, which should be set to `/var/task/`. - -> **Note:** A Vercel project connected to a [Git integration](https://vercel.com/docs/platform/deployments#git) is required before adding the Sentry integration. - -#### Without Using Vercel - -1. Set up the `NEXT_PUBLIC_SENTRY_DSN` environment variable as described above. -2. Save your Sentry organization slug as the `SENTRY_ORG` environment variable and your project slug as the `SENTRY_PROJECT` environment variable in `.env.local`. -3. Save your git provider's commit SHA as `VERCEL_GIT_COMMIT_SHA` environment variable in `.env.local`. -4. Create an auth token in Sentry. The recommended way to do this is by creating a new internal integration for your organization. To do so, go into **Settings > Developer Settings > New internal integration**. After the integration is created, copy the Token. -5. Save the token inside the `SENTRY_AUTH_TOKEN` environment variable in `.env.local`. -6. Set `NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR` to the absolute path of the folder the Next.js app is running from - -> **Note:** Sourcemap upload is disabled in development mode using the `NODE_ENV` environment variable. To change this behavior, remove the `NODE_ENV === 'production'` check from your `next.config.js` file. - -## Other configuration options - -More configurations are available for the [Sentry webpack plugin](https://github.com/getsentry/sentry-webpack-plugin) using [Sentry Configuration variables](https://docs.sentry.io/cli/configuration/) for defining the releases/verbosity/etc. - -## Notes - -- By default, neither sourcemaps nor error tracking are enabled in development mode (see Configuration). -- When enabled in development mode, error handling [works differently than in production](https://nextjs.org/docs/advanced-features/custom-error-page#customizing-the-error-page) as `_error.js` is never actually called. -- The build output will contain warning about unhandled Promise rejections. This is caused by the test pages, and is expected. When deploying to Vercel, "Client Error 1" will actually be sent to Sentry during the build, while that test page is being statically rendered. -- By default, source maps are uploaded to [sentry.io](https://sentry.io). If you're self-hosting Sentry, add `SENTRY_URL` to `.env` or `.env.locale` and set it to the base domain of your installation, which by default is `https://sentry.io/`. +You can [configure your app automatically](https://docs.sentry.io/platforms/javascript/guides/nextjs/#configure) or do the [manual setup](https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/). +Both ways lead to having your custom config files (`next.config.js`, `sentry.client.config.js`, `sentry.server.config.js`, and `sentry.properties`); so you can delete them from the example, they are here to illustrate how an example app looks like. ## Deploy on Vercel @@ -96,3 +45,7 @@ To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [ Alternatively, you can deploy using our template by clicking on the Deploy button below. [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-sentry&project-name=with-sentry&repository-name=with-sentry&env=NEXT_PUBLIC_SENTRY_DSN&envDescription=DSN%20Key%20required%20by%20Sentry&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-sentry%23step-1-enable-error-tracking) + +## Sentry Integration + +Sentry’s Vercel Integration connects your Sentry and Vercel projects to automatically upload source maps and notify Sentry of release deployment. Learn more about this integration in [Sentry’s full documentation](https://docs.sentry.io/product/integrations/vercel/). diff --git a/examples/with-sentry/next.config.js b/examples/with-sentry/next.config.js index f23bbaa222670..a5aae5a754546 100644 --- a/examples/with-sentry/next.config.js +++ b/examples/with-sentry/next.config.js @@ -1,78 +1,24 @@ -// 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_GIT_COMMIT_SHA, -} = process.env +// This file sets a custom webpack configuration to use your Next.js app +// with Sentry. +// https://nextjs.org/docs/api-reference/next.config.js/introduction +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ -process.env.SENTRY_DSN = SENTRY_DSN -const basePath = '' +const { withSentryConfig } = require('@sentry/nextjs') -module.exports = { - productionBrowserSourceMaps: true, - env: { - // Make the COMMIT_SHA available to the client so that Sentry events can be - // marked for the release they belong to. It may be undefined if running - // outside of Vercel - NEXT_PUBLIC_COMMIT_SHA: VERCEL_GIT_COMMIT_SHA, - }, - 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' - } - - // Define an environment variable so source code can check whether or not - // it's running on the server so we can correctly initialize Sentry - config.plugins.push( - new options.webpack.DefinePlugin({ - 'process.env.NEXT_IS_SERVER': JSON.stringify( - options.isServer.toString() - ), - }) - ) +const moduleExports = { + // Your existing module.exports +} - // 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 && - VERCEL_GIT_COMMIT_SHA && - NODE_ENV === 'production' - ) { - config.plugins.push( - new SentryWebpackPlugin({ - include: '.next', - ignore: ['node_modules'], - stripPrefix: ['webpack://_N_E/'], - urlPrefix: `~${basePath}/_next`, - release: VERCEL_GIT_COMMIT_SHA, - }) - ) - } - return config - }, - basePath, +const SentryWebpackPluginOptions = { + // Additional config options for the Sentry Webpack plugin. Keep in mind that + // the following options are set automatically, and overriding them is not + // recommended: + // release, url, org, project, authToken, configFile, stripPrefix, + // urlPrefix, include, ignore + // For all available options, see: + // https://github.com/getsentry/sentry-webpack-plugin#options. } + +// Make sure adding Sentry options is the last code to run before exporting, to +// ensure that your source maps include changes from all other Webpack plugins +module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions) diff --git a/examples/with-sentry/package.json b/examples/with-sentry/package.json index ceeab004b5515..1a3d3f5796afc 100644 --- a/examples/with-sentry/package.json +++ b/examples/with-sentry/package.json @@ -8,10 +8,7 @@ "start": "next start" }, "dependencies": { - "@sentry/browser": "^5.21.3", - "@sentry/integrations": "^5.21.3", - "@sentry/node": "^5.21.3", - "@sentry/webpack-plugin": "^1.12.1", + "@sentry/nextjs": "^6.3.5", "next": "latest", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/examples/with-sentry/pages/_app.js b/examples/with-sentry/pages/_app.js index 98d5042c2e26b..2ff89fd9b6fab 100644 --- a/examples/with-sentry/pages/_app.js +++ b/examples/with-sentry/pages/_app.js @@ -1,7 +1,3 @@ -import { init } from '../utils/sentry' - -init() - export default function App({ Component, pageProps, err }) { // Workaround for https://github.com/vercel/next.js/issues/8592 return diff --git a/examples/with-sentry/pages/_error.js b/examples/with-sentry/pages/_error.js index f34ff9cea2e60..003b2c873a255 100644 --- a/examples/with-sentry/pages/_error.js +++ b/examples/with-sentry/pages/_error.js @@ -1,5 +1,6 @@ import NextErrorComponent from 'next/error' -import * as Sentry from '@sentry/node' + +import * as Sentry from '@sentry/nextjs' const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => { if (!hasGetInitialPropsRun && err) { diff --git a/examples/with-sentry/pages/api/test1.js b/examples/with-sentry/pages/api/test1.js index 546d3215d0714..461c2353bbb52 100644 --- a/examples/with-sentry/pages/api/test1.js +++ b/examples/with-sentry/pages/api/test1.js @@ -1,10 +1,10 @@ -import { init } from '../../utils/sentry' - -init() +import { withSentry } from '@sentry/nextjs' const doAsyncWork = () => Promise.reject(new Error('API Test 1')) doAsyncWork() -export default async function handler(req, res) { +async function handler(req, res) { res.status(200).json({ name: 'John Doe' }) } + +export default withSentry(handler) diff --git a/examples/with-sentry/pages/api/test2.js b/examples/with-sentry/pages/api/test2.js index 3df4ab6372896..87001cffcf9fc 100644 --- a/examples/with-sentry/pages/api/test2.js +++ b/examples/with-sentry/pages/api/test2.js @@ -1,6 +1,4 @@ -import { init } from '../../utils/sentry' - -init() +import { withSentry } from '@sentry/nextjs' function work() { throw new Error('API Test 2') @@ -8,6 +6,8 @@ function work() { work() -export default async function handler(req, res) { +async function handler(req, res) { res.status(200).json({ name: 'John Doe' }) } + +export default withSentry(handler) diff --git a/examples/with-sentry/pages/api/test3.js b/examples/with-sentry/pages/api/test3.js index 152c058a33df2..13cf097741580 100644 --- a/examples/with-sentry/pages/api/test3.js +++ b/examples/with-sentry/pages/api/test3.js @@ -1,13 +1,13 @@ -import { init } from '../../utils/sentry' - -init() +import { withSentry } from '@sentry/nextjs' function work() { throw new Error('API Test 3') } -export default async function handler(req, res) { +async function handler(req, res) { work() res.status(200).json({ name: 'John Doe' }) } + +export default withSentry(handler) diff --git a/examples/with-sentry/pages/api/test4.js b/examples/with-sentry/pages/api/test4.js index 699569c781b38..f4724110c0e10 100644 --- a/examples/with-sentry/pages/api/test4.js +++ b/examples/with-sentry/pages/api/test4.js @@ -1,10 +1,6 @@ -import * as Sentry from '@sentry/node' +import * as Sentry from '@sentry/nextjs' -import { init } from '../../utils/sentry' - -init() - -export default async function handler(req, res) { +async function handler(req, res) { try { throw new Error('API Test 4') } catch (error) { @@ -16,3 +12,5 @@ export default async function handler(req, res) { await Sentry.flush(2000) res.status(200).json({ name: 'John Doe' }) } + +export default Sentry.withSentry(handler) diff --git a/examples/with-sentry/pages/ssr/test4.js b/examples/with-sentry/pages/ssr/test4.js index e828894825c61..737339f85dd3d 100644 --- a/examples/with-sentry/pages/ssr/test4.js +++ b/examples/with-sentry/pages/ssr/test4.js @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/node' +import * as Sentry from '@sentry/nextjs' const Test4 = () =>

SSR Test 4

diff --git a/examples/with-sentry/sentry.client.config.js b/examples/with-sentry/sentry.client.config.js new file mode 100644 index 0000000000000..eca167eb76fdf --- /dev/null +++ b/examples/with-sentry/sentry.client.config.js @@ -0,0 +1,14 @@ +// This file configures the intialization of Sentry on the browser. +// The config you add here will be used whenever a page is visited. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs' + +const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN + +Sentry.init({ + dsn: SENTRY_DSN, + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps +}) diff --git a/examples/with-sentry/sentry.properties b/examples/with-sentry/sentry.properties new file mode 100644 index 0000000000000..3837f095ce042 --- /dev/null +++ b/examples/with-sentry/sentry.properties @@ -0,0 +1,5 @@ +defaults.url=# your base url +defaults.org=# your org +defaults.project=# your project +auth.token=# your auth token +cli.executable=# [optional] path to the cli executable diff --git a/examples/with-sentry/sentry.server.config.js b/examples/with-sentry/sentry.server.config.js new file mode 100644 index 0000000000000..f1ff06b8e5eed --- /dev/null +++ b/examples/with-sentry/sentry.server.config.js @@ -0,0 +1,14 @@ +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever the server handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs' + +const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN + +Sentry.init({ + dsn: SENTRY_DSN, + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps +}) diff --git a/examples/with-sentry/utils/sentry.js b/examples/with-sentry/utils/sentry.js deleted file mode 100644 index 22fe73507c192..0000000000000 --- a/examples/with-sentry/utils/sentry.js +++ /dev/null @@ -1,35 +0,0 @@ -import * as Sentry from '@sentry/node' -import { RewriteFrames } from '@sentry/integrations' - -export const init = () => { - if (process.env.NEXT_PUBLIC_SENTRY_DSN) { - const integrations = [] - if ( - process.env.NEXT_IS_SERVER === 'true' && - process.env.NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR - ) { - // For Node.js, rewrite Error.stack to use relative paths, so that source - // maps starting with ~/_next map to files in Error.stack with path - // app:///_next - integrations.push( - new RewriteFrames({ - iteratee: (frame) => { - frame.filename = frame.filename.replace( - process.env.NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR, - 'app:///' - ) - frame.filename = frame.filename.replace('.next', '_next') - return frame - }, - }) - ) - } - - Sentry.init({ - enabled: process.env.NODE_ENV === 'production', - integrations, - dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - release: process.env.NEXT_PUBLIC_COMMIT_SHA, - }) - } -}