diff --git a/docs/platforms/javascript/guides/nextjs/configuration/build/index.mdx b/docs/platforms/javascript/guides/nextjs/configuration/build/index.mdx index ee94a816e83c2..aad7e00f1e30b 100644 --- a/docs/platforms/javascript/guides/nextjs/configuration/build/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/configuration/build/index.mdx @@ -16,7 +16,7 @@ keywords: ] --- -The Sentry Next.js SDK supports automatic code injection and source map upload during your app's build process using the `withSentryConfig` wrapper in your Next.js configuration file (`next.config.js` or `next.config.mjs`). For information on updating the configuration, see [Extend Next.js Configuration](../../manual-setup/#extend-your-nextjs-configuration). +The Sentry Next.js SDK supports automatic code injection and source map upload during your app's build process using the `withSentryConfig` wrapper in your Next.js configuration file (`next.config.js` or `next.config.mjs`). For information on updating the configuration, see the [Manual Setup guide](/platforms/javascript/guides/nextjs/manual-setup/#configure). ## Available Options diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index ecbbfc013d52b..f94716c8371eb 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -1,6 +1,6 @@ --- title: "Next.js" -description: Learn how to set up and configure Sentry in your Next.js application using the installation wizard, capture your first errors, and view them in Sentry. +description: Learn how to set up and configure Sentry in your Next.js application using the installation wizard, capture your first errors, logs and traces and view them in Sentry. sdk: sentry.javascript.nextjs categories: - javascript @@ -17,214 +17,202 @@ categories: ## Install - - - +Run the Sentry wizard to automatically configure Sentry in your Next.js application: -To install Sentry using the installation wizard, run the command on the right within your project directory. +```bash +npx @sentry/wizard@latest -i nextjs +``` -The wizard guides you through the setup process, asking you to enable additional (optional) Sentry features for your application beyond error monitoring. + -This guide assumes that you enable all features and allow the wizard to create an example page and route. You can add or remove features at any time, but setting them up now will save you the effort of configuring them manually later. +The wizard will guide you through: - +- Selecting or creating your Sentry project +- Installing the `@sentry/nextjs` package +- Enabling optional features ([Tracing](/platforms/javascript/guides/nextjs/tracing/), [Session Replay](/platforms/javascript/guides/nextjs/session-replay/), [Logs](/platforms/javascript/guides/nextjs/logs/)) -- Creates config files with the default `Sentry.init()` calls for all runtimes (Node.js, Browser, and Edge) -- Adds a Next.js instrumentation hook to your project (`instrumentation.ts`) -- Creates or updates your Next.js config with the default Sentry settings -- Creates error handling components (`global-error.(jsx|tsx)` and `_error.jsx` for the Pages Router) if they don't already exist -- Creates `.sentryclirc` with an auth token to upload source maps (this file is automatically added to `.gitignore`) -- Adds an example page and route to your application to help verify your Sentry setup +It automatically creates and configures the following files: - +| File | Purpose | +| ----------------------------- | --------------------------------------------------------- | +| `instrumentation-client.ts` | Client-side SDK initialization (browser) | +| `sentry.server.config.ts` | Server-side SDK initialization (Node.js) | +| `sentry.edge.config.ts` | Edge runtime SDK initialization | +| `instrumentation.ts` | Next.js instrumentation hook | +| `next.config.ts` | Updated with Sentry configuration | +| `global-error.tsx` | App Router error boundary | +| `app/sentry-example-page/` | Example page to test your setup | +| `app/api/sentry-example-api/` | Example API route | +| `.env.sentry-build-plugin` | Auth token for source map uploads (added to `.gitignore`) | - - +**Why multiple configuration files?** Next.js runs code in different environments, each requiring separate Sentry initialization: -```bash -npx @sentry/wizard@latest -i nextjs -``` +- **Client** (`instrumentation-client.ts`) โ€” Runs in the browser, captures frontend errors, performance, and replay +- **Server** (`sentry.server.config.ts`) โ€” Runs in Node.js, captures backend errors and API route issues +- **Edge** (`sentry.edge.config.ts`) โ€” Runs in edge runtimes (middleware, edge API routes) - - - - -## Configure + -If you prefer to configure Sentry manually, here are the configuration files the wizard would create: +Prefer to set things up yourself? Check out the [Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) guide. -In addition to capturing errors, you can monitor interactions between multiple services or applications by [enabling tracing](/concepts/key-terms/tracing/). You can also get to the root of an error or performance issue faster, by watching a video-like reproduction of a user session with [session replay](/product/explore/session-replay/web/getting-started/). +## Verify -Select which Sentry features you'd like to install in addition to Error Monitoring to get the corresponding installation and configuration instructions below. +Select the features you enabled in the wizard, then work through the checklist to verify everything is working: - - - - - - -### Client-Side Configuration - -The wizard creates a client configuration file that initializes the Sentry SDK in your browser. - -The configuration includes your DSN (Data Source Name), which connects your app to your Sentry project, and enables the features you selected during installation. + - - + + +The wizard created these files. Check they exist and contain your DSN: -```javascript {tabTitle:Client} {filename:instrumentation-client.(js|ts)} +```typescript {tabTitle:Client} {filename:instrumentation-client.ts} import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", - - // Adds request headers and IP for users, for more info visit: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii - sendDefaultPii: true, - - integrations: [ - // ___PRODUCT_OPTION_START___ session-replay - // Replay may only be enabled for the client-side - Sentry.replayIntegration(), - // ___PRODUCT_OPTION_END___ session-replay - // ___PRODUCT_OPTION_START___ user-feedback - Sentry.feedbackIntegration({ - // Additional SDK configuration goes in here, for example: - colorScheme: "system", - }), - // ___PRODUCT_OPTION_END___ user-feedback - ], - // ___PRODUCT_OPTION_START___ logs - - // Enable logs to be sent to Sentry - enableLogs: true, - // ___PRODUCT_OPTION_END___ logs - // ___PRODUCT_OPTION_START___ performance - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for tracing. - // We recommend adjusting this value in production - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate tracesSampleRate: 1.0, // ___PRODUCT_OPTION_END___ performance // ___PRODUCT_OPTION_START___ session-replay - // Capture Replay for 10% of all - // plus for 100% of sessions with an error - // Learn more at - // https://docs.sentry.io/platforms/javascript/session-replay/configuration/#general-integration-configuration replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, // ___PRODUCT_OPTION_END___ session-replay + // ___PRODUCT_OPTION_START___ logs + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs + integrations: [ + // ___PRODUCT_OPTION_START___ session-replay + Sentry.replayIntegration(), + // ___PRODUCT_OPTION_END___ session-replay + ], }); - -// ___PRODUCT_OPTION_START___ performance -export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; -// ___PRODUCT_OPTION_END___ performance ``` - - - - - - -### Server-Side Configuration - -The wizard also creates a server configuration file for Node.js and Edge runtimes. - -For more advanced configuration options or to set up Sentry manually, check out our [manual setup guide](/platforms/javascript/guides/nextjs/manual-setup/). - - - - -```javascript {tabTitle:Server} {filename:sentry.server.config.(js|ts)} +```typescript {tabTitle:Server} {filename:sentry.server.config.ts} import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", - - // Adds request headers and IP for users, for more info visit: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii - sendDefaultPii: true, + // ___PRODUCT_OPTION_START___ performance + tracesSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ performance // ___PRODUCT_OPTION_START___ logs - - // Enable logs to be sent to Sentry enableLogs: true, // ___PRODUCT_OPTION_END___ logs +}); +``` + +```typescript {tabTitle:Edge} {filename:sentry.edge.config.ts} +import * as Sentry from "@sentry/nextjs"; +Sentry.init({ + dsn: "___PUBLIC_DSN___", // ___PRODUCT_OPTION_START___ performance - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for tracing. - // We recommend adjusting this value in production - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate tracesSampleRate: 1.0, // ___PRODUCT_OPTION_END___ performance }); ``` - - - - -## Verify Your Setup - - + + +Visit `/sentry-example-page` in your browser and click the "Throw Sample Error" button. -If you haven't tested your Sentry configuration yet, let's do it now. You can confirm that Sentry is working properly and sending data to your Sentry project by using the example page and route created by the installation wizard: +If you don't have an example page, add this button to any page: -1. Open the example page `/sentry-example-page` in your browser. For most Next.js applications, this will be at localhost. -2. Click the "Throw error" button. This triggers two errors: - -- a frontend error -- an error within the API route - -Sentry captures both of these errors for you. Additionally, the button click starts a performance trace to measure the time it takes for the API request to complete. - - - -Don't forget to explore the example files' code in your project to understand what's happening after your button click. - - +```tsx +"use client"; +import * as Sentry from "@sentry/nextjs"; -### View Captured Data in Sentry +export function TestButton() { + return ( + + ); +} +``` -Now, head over to your project on [Sentry.io](https://sentry.io) to view the collected data (it takes a couple of moments for the data to appear). + + + +You can also create custom spans to measure specific operations: + +```typescript +await Sentry.startSpan({ name: "my-operation", op: "task" }, async () => { + await doSomething(); +}); +``` - + + + +Send structured logs from anywhere in your application: + +```typescript +Sentry.logger.info("User action", { userId: "123" }); +Sentry.logger.warn("Slow response", { duration: 5000 }); +Sentry.logger.error("Operation failed", { reason: "timeout" }); +``` - + + ## Next Steps -At this point, you should have integrated Sentry into your Next.js application and should already be sending error and performance data to your Sentry project. - -Now's a good time to customize your setup and look into more advanced topics. -Our next recommended steps for you are: +You've successfully integrated Sentry into your Next.js application! Here's what to explore next: -- Learn about [instrumenting Next.js server actions](/platforms/javascript/guides/nextjs/apis/#server-actions) -- Learn how to [manually capture errors](/platforms/javascript/guides/nextjs/usage/) -- Continue to [customize your configuration](/platforms/javascript/guides/nextjs/configuration/) -- Get familiar with [Sentry's product features](/product) like tracing, insights, and alerts -- Learn how to [set up Sentry for Vercel's micro frontends](/platforms/javascript/guides/nextjs/best-practices/micro-frontends/) -- Learn more about our [Vercel integration](/organization/integrations/deployment/vercel/) - - - -- If you encountered issues with our installation wizard, try [setting up Sentry manually](/platforms/javascript/guides/nextjs/manual-setup/) -- [Get support](https://sentry.zendesk.com/hc/en-us/) - - +- [Logs Integrations](/platforms/javascript/guides/nextjs/logs/#integrations) - Connect popular logging libraries like Pino, Winston, and Bunyan +- [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/distributed-tracing/) - Trace requests across services and microservices +- [AI Agent Monitoring](/platforms/javascript/guides/nextjs/tracing/instrumentation/ai-agents-module/) - Monitor AI agents built with Vercel AI SDK, LangChain, and more +- [Connect GitHub + Seer](/organization/integrations/source-code-mgmt/github/#installing-github) - Enable AI-powered [root cause analysis](/product/ai-in-sentry/seer/) by connecting your GitHub repository +- [Configuration Options](/platforms/javascript/guides/nextjs/configuration/) - Explore extended SDK configuration options diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup.mdx deleted file mode 100644 index 3e949a48199d1..0000000000000 --- a/docs/platforms/javascript/guides/nextjs/manual-setup.mdx +++ /dev/null @@ -1,637 +0,0 @@ ---- -title: "Manual Setup" -sidebar_order: 1 -description: "Learn how to manually set up Sentry in your Next.js app and capture your first errors." ---- - - - For the fastest setup, we recommend using the [wizard - installer](/platforms/javascript/guides/nextjs). - - - - -## Step 1: Install - -### Install the Sentry SDK - -Run the command for your preferred package manager to add the Sentry SDK to your application: - -```bash {tabTitle:npm} -npm install @sentry/nextjs --save -``` - -```bash {tabTitle:yarn} -yarn add @sentry/nextjs -``` - -```bash {tabTitle:pnpm} -pnpm add @sentry/nextjs -``` - -## Step 2: Configure - -Choose the features you want to configure, and this guide will show you how: - - - - - -### Apply Instrumentation to Your App - -Extend your app's default Next.js options by adding `withSentryConfig` into your `next.config.(js|mjs)` file: - -```JavaScript {tabTitle:CJS} {filename:next.config.js} -const { withSentryConfig } = require("@sentry/nextjs"); - -const nextConfig = { - // Your existing Next.js configuration -}; - -// Make sure adding Sentry options is the last code to run before exporting -module.exports = withSentryConfig(nextConfig, { - org: "___ORG_SLUG___", - project: "___PROJECT_SLUG___", - - // Only print logs for uploading source maps in CI - // Set to `true` to suppress logs - silent: !process.env.CI, - - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, -}); -``` - -```JavaScript {tabTitle:ESM} {filename:next.config.mjs} -import { withSentryConfig } from "@sentry/nextjs"; - -const nextConfig = { - // Your existing Next.js configuration -}; - -// Make sure adding Sentry options is the last code to run before exporting -export default withSentryConfig(nextConfig, { - org: "___ORG_SLUG___", - project: "___PROJECT_SLUG___", - - // Only print logs for uploading source maps in CI - // Set to `true` to suppress logs - silent: !process.env.CI, - - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, -}); -``` - - - -### Initialize Sentry Client-Side and Server-Side SDKs - -Create three files in your application's root directory: - -- `sentry.server.config.(js|ts)` -- `sentry.edge.config.(js|ts)` -- `instrumentation-client.(js|ts)` - - If you previously had a file called `sentry.client.config.(js|ts)`, you can safely rename this to `instrumentation-client.(js|ts)` for all Next.js versions. - -Add the following initialization code into each respective file: - - - These files run in different environments (browser, server, edge) and are - slightly different, so copy them carefully. - - -```javascript {tabTitle:Client} {filename:instrumentation-client.(js|ts)} -import * as Sentry from "@sentry/nextjs"; - -Sentry.init({ - dsn: "___PUBLIC_DSN___", - - // Adds request headers and IP for users, for more info visit: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii - sendDefaultPii: true, - // ___PRODUCT_OPTION_START___ performance - - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for tracing. - // We recommend adjusting this value in production - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate - tracesSampleRate: 1.0, - // ___PRODUCT_OPTION_END___ performance - integrations: [ - // ___PRODUCT_OPTION_START___ session-replay - // Replay may only be enabled for the client-side - Sentry.replayIntegration(), - // ___PRODUCT_OPTION_END___ session-replay - // ___PRODUCT_OPTION_START___ user-feedback - Sentry.feedbackIntegration({ - // Additional SDK configuration goes in here, for example: - colorScheme: "system", - }), - // ___PRODUCT_OPTION_END___ user-feedback - ], - // ___PRODUCT_OPTION_START___ session-replay - - // Capture Replay for 10% of all sessions, - // plus for 100% of sessions with an error - // Learn more at - // https://docs.sentry.io/platforms/javascript/session-replay/configuration/#general-integration-configuration - replaysSessionSampleRate: 0.1, - replaysOnErrorSampleRate: 1.0, - // ___PRODUCT_OPTION_END___ session-replay - // ___PRODUCT_OPTION_START___ logs - - // Enable logs to be sent to Sentry - enableLogs: true, - // ___PRODUCT_OPTION_END___ logs - // 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 -}); - -// This export will instrument router navigations, and is only relevant if you enable tracing. -// `captureRouterTransitionStart` is available from SDK version 9.12.0 onwards -export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; -``` - -```javascript {tabTitle:Server} {filename:sentry.server.config.(js|ts)} -import * as Sentry from "@sentry/nextjs"; - -Sentry.init({ - dsn: "___PUBLIC_DSN___", - - // Adds request headers and IP for users, for more info visit: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii - sendDefaultPii: true, - // ___PRODUCT_OPTION_START___ performance - - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for tracing. - // We recommend adjusting this value in production - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate - tracesSampleRate: 1.0, - // ___PRODUCT_OPTION_END___ performance - // ___PRODUCT_OPTION_START___ logs - - // Enable logs to be sent to Sentry - enableLogs: true, - // ___PRODUCT_OPTION_END___ logs - - // ... - - // 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 -}); -``` - -```javascript {tabTitle:Edge} {filename:sentry.edge.config.(js|ts)} -import * as Sentry from "@sentry/nextjs"; - -Sentry.init({ - dsn: "___PUBLIC_DSN___", - - // Adds request headers and IP for users, for more info visit: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii - sendDefaultPii: true, - // ___PRODUCT_OPTION_START___ performance - - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for tracing. - // We recommend adjusting this value in production - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate - tracesSampleRate: 1.0, - // ___PRODUCT_OPTION_END___ performance - // ___PRODUCT_OPTION_START___ logs - - // Enable logs to be sent to Sentry - enableLogs: true, - // ___PRODUCT_OPTION_END___ logs - - // ... - - // 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 -}); -``` - - - Include your DSN directly in these files, or use a _public_ environment - variable like `NEXT_PUBLIC_SENTRY_DSN`. - - -### Register Sentry Server-Side SDK Initialization - -Create a [Next.js Instrumentation file](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation) named `instrumentation.(js|ts)` in your project root or inside the `src` folder if you have one. Import your server and edge configurations, making sure that the imports point to your specific files: - -```javascript {filename:instrumentation.(js|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"); - } -} -``` - - - If you want the Sentry SDK to be available on the server side and not on the - client side, simply delete `instrumentation-client.(js|ts)`. This will prevent - webpack from pulling in the Sentry-related files when generating the browser - bundle. Similarly, if you want to opt out of server-side SDK bundling, delete - the `sentry.server.config.js` and `sentry.edge.config.js` files. Make sure to - remove any imports of these files from `instrumentation.ts`. - - -## Step 3: Capture React Render Errors - -To capture React render errors, you need to add error components for the App Router and the Pages Router. - -### App Router - -Create or update the `global-error.(tsx|jsx)` file to define a [custom Next.js GlobalError component](https://nextjs.org/docs/app/building-your-application/routing/error-handling): - -```tsx {filename:global-error.tsx} -"use client"; - -import * as Sentry from "@sentry/nextjs"; -import NextError from "next/error"; -import { useEffect } from "react"; - -export default function GlobalError({ - error, -}: { - error: Error & { digest?: string }; -}) { - useEffect(() => { - Sentry.captureException(error); - }, [error]); - - return ( - - - {/* `NextError` is the default Next.js error page component. Its type - definition requires a `statusCode` prop. However, since the App Router - does not expose status codes for errors, we simply pass 0 to render a - generic error message. */} - - - - ); -} -``` - -```jsx {filename:global-error.jsx} -"use client"; - -import * as Sentry from "@sentry/nextjs"; -import NextError from "next/error"; -import { useEffect } from "react"; - -export default function GlobalError({ error }) { - useEffect(() => { - Sentry.captureException(error); - }, [error]); - - return ( - - - {/* This is the default Next.js error component. */} - - - - ); -} -``` - -#### Errors from Nested React Server Components - - - Requires `@sentry/nextjs` version `8.28.0` or higher and Next.js 15. - - -To capture errors from nested React Server Components, use the [`onRequestError`](https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation#onrequesterror-optional) hook in `instrumentation.(js|ts)` and pass all arguments to the `captureRequestError` function: - -```TypeScript {filename:instrumentation.ts} -import * as Sentry from "@sentry/nextjs"; - -export const onRequestError = Sentry.captureRequestError; -``` - -```JavaScript {filename:instrumentation.js} -import * as Sentry from "@sentry/nextjs"; - -export const onRequestError = Sentry.captureRequestError; -``` - - -You can call `captureRequestError` with all arguments passed to `onRequestError`: - -```TypeScript {filename:instrumentation.ts} -import * as Sentry from "@sentry/nextjs"; -import type { Instrumentation } from "next"; - -export const onRequestError: Instrumentation.onRequestError = (...args) => { - Sentry.captureRequestError(...args); - - // ... additional logic here -}; -``` - -```JavaScript {filename:instrumentation.js} -import * as Sentry from "@sentry/nextjs"; - -export const onRequestError = (...args) => { - Sentry.captureRequestError(...args); - - // ... additional logic here -}; -``` - - - -### Pages Router - -Create or update the `_error.(tsx|jsx)` file to define a [custom Next.js error page](https://nextjs.org/docs/pages/building-your-application/routing/custom-error) for the Pages Router like so: - -```tsx {filename:_error.tsx} -import * as Sentry from "@sentry/nextjs"; -import type { NextPage } from "next"; -import type { ErrorProps } from "next/error"; -import Error from "next/error"; - -const CustomErrorComponent: NextPage = (props) => { - return ; -}; - -CustomErrorComponent.getInitialProps = async (contextData) => { - // In case this is running in a serverless function, await this in order to give Sentry - // time to send the error before the lambda exits - await Sentry.captureUnderscoreErrorException(contextData); - - // This will contain the status code of the response - return Error.getInitialProps(contextData); -}; - -export default CustomErrorComponent; -``` - -```jsx {filename:_error.jsx} -import * as Sentry from "@sentry/nextjs"; -import type { NextPage } from "next"; -import type { ErrorProps } from "next/error"; -import Error from "next/error"; - -const CustomErrorComponent: NextPage = (props) => { - return ; -}; - -CustomErrorComponent.getInitialProps = async (contextData) => { - // In case this is running in a serverless function, await this in order to give Sentry - // time to send the error before the lambda exits - await Sentry.captureUnderscoreErrorException(contextData); - - // This will contain the status code of the response - return Error.getInitialProps(contextData); -}; - -export default CustomErrorComponent; -``` - -## Step 4: Add Readable Stack Traces With Source Maps (Optional) - -Sentry can automatically provide readable stack traces for errors using source maps, requiring a Sentry auth token. - -Update your `next.config.(js|mjs)` file with the following options: - -```javascript {tabTitle:ESM} {filename:next.config.mjs} -export default withSentryConfig(nextConfig, { - org: "___ORG_SLUG___", - project: "___PROJECT_SLUG___", - - // Pass the auth token - authToken: process.env.SENTRY_AUTH_TOKEN, - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, -}); -``` - -```javascript {tabTitle:CJS} {filename:next.config.js} -module.exports = withSentryConfig(nextConfig, { - org: "___ORG_SLUG___", - project: "___PROJECT_SLUG___", - - // Pass the auth token - authToken: process.env.SENTRY_AUTH_TOKEN, - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, -}); -``` - -Set the `SENTRY_AUTH_TOKEN` environment variable in your `.env` file: - -```sh {filename:.env} -SENTRY_AUTH_TOKEN=___ORG_AUTH_TOKEN___ -``` - - - -Make sure to keep your auth token secret and out of version control. - - - -## Step 5: Avoid Ad Blockers With Tunneling (Optional) - -You can prevent ad blockers from blocking Sentry events using tunneling. Use the `tunnelRoute` option to add an API endpoint in your application that forwards Sentry events to Sentry servers. - -For better ad-blocker evasion, you can either: - -- Set `tunnelRoute: true` to automatically generate a random tunnel route for each build, making it harder for ad-blockers to detect and block monitoring requests -- Set `tunnelRoute: "/my-tunnel-route"` to use a static route of your choosing - -```javascript {tabTitle:ESM} {filename:next.config.mjs} -export default withSentryConfig(nextConfig, { - // 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: true, // Generates a random route for each build (recommended) -}); -``` - -```javascript {tabTitle:CJS} {filename:next.config.js} -module.exports = withSentryConfig(nextConfig, { - // 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: true, // Generates a random route for each build (recommended) -}); -``` - - - If you're using Turbopack, client-side event recording will fail if your - Next.js middleware intercepts the configured tunnel route. To fix this, set - the route to a fixed string (like `/error-monitoring`) and add a negative - matcher like `(?!error-monitoring)` in your middleware to exclude the tunnel - route. If you're not using Turbopack, Sentry will automatically skip the - tunnel route in your middleware. - - -## Step 6: Instrument Vercel Cron Jobs (Optional) - -You can automatically create [Cron Monitors](/product/crons/) in Sentry if you have configured [Vercel cron jobs](https://vercel.com/docs/cron-jobs). - -Update `withSentryConfig` in your `next.config.(js|mjs)` file with the following option: - -```javascript {tabTitle:ESM} {filename:next.config.mjs} -export default withSentryConfig(nextConfig, { - automaticVercelMonitors: true, -}); -``` - -```javascript {tabTitle:CJS} {filename:next.config.js} -module.exports = withSentryConfig(nextConfig, { - automaticVercelMonitors: true, -}); -``` - - - -Automatic Vercel cron jobs instrumentation currently only supports the Pages Router. App Router route handlers are not yet supported. - - - -## Step 7: Capture React Component Names (Optional) - -You can capture React component names to see which component a user clicked on in Sentry features like Session Replay. -Update `withSentryConfig` in your `next.config.(js|mjs)` file with the following option: - -```javascript {tabTitle:ESM} {filename:next.config.mjs} -export default withSentryConfig(nextConfig, { - reactComponentAnnotation: { - enabled: true, - }, -}); -``` - -```javascript {tabTitle:CJS} {filename:next.config.js} -module.exports = withSentryConfig(nextConfig, { - reactComponentAnnotation: { - enabled: true, - }, -}); -``` - -## Step 8: Verify - - - -Let's test your setup and confirm that Sentry is working correctly and sending data to your Sentry project. - -### Issues - -To verify that Sentry captures errors and creates issues in your Sentry project, add a test button to an existing page or create a new one: - -```jsx - -``` - - - Open the page in a browser (for most Next.js applications, this will be at - localhost) and click the button to trigger a frontend error. - - - - - -### Tracing - -To test tracing, create a test API route like `/api/sentry-example-api`: - -```javascript -import { NextResponse } from "next/server"; -export const dynamic = "force-dynamic"; - -// A faulty API route to test Sentry's error monitoring -export function GET() { - throw new Error("Sentry Example API Route Error"); - return NextResponse.json({ data: "Testing Sentry Error..." }); -} -``` - -Next, update your test button to call this route and throw an error if the response isn't `ok`: - -```jsx -; -``` - -Open the page in a browser (for most Next.js applications, this will be at localhost) and click the button to trigger two errors: - -- a frontend error -- an error within the API route - -Additionally, this starts a performance trace to measure the time it takes for the API request to complete. - - - -### View Captured Data in Sentry - -Now, head over to your project on [Sentry.io](https://sentry.io) to view the collected data (it takes a couple of moments for the data to appear). - - - -## Next Steps - -At this point, you should have integrated Sentry into your Next.js application and should already be sending data to your Sentry project. - -Now's a good time to customize your setup and look into more advanced topics. -Our next recommended steps for you are: - -- Learn about [instrumenting Next.js server actions](/platforms/javascript/guides/nextjs/apis/#server-actions) -- Configure [server-side auto-instrumentation](/platforms/javascript/guides/nextjs/configuration/build/#nextjs-specific-options) -- Learn how to [manually capture errors](/platforms/javascript/guides/nextjs/usage/) -- Continue to [customize your configuration](/platforms/javascript/guides/nextjs/configuration/) -- Learn more about our [Vercel integration](/organization/integrations/deployment/vercel/) -- Learn how to [configure Next.js with Sentry for Cloudflare Workers](/platforms/javascript/guides/nextjs/best-practices/deploying-on-cloudflare/) - - - -- If you encountered issues with setting up Sentry manually, [try our installation wizard](/platforms/javascript/guides/nextjs/) -- [Get support](https://sentry.zendesk.com/hc/en-us/) - - diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx new file mode 100644 index 0000000000000..454eb8d7deab5 --- /dev/null +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx @@ -0,0 +1,658 @@ +--- +title: "Manual Setup" +sidebar_order: 1 +description: "Learn how to set up and configure Sentry in your Next.js application, capture your first errors, logs and traces and view them in Sentry" +sdk: sentry.javascript.nextjs +categories: + - javascript + - browser + - server + - server-node +--- + + + For the fastest setup, we recommend using the [wizard + installer](/platforms/javascript/guides/nextjs). + + +This guide covers manual setup for **Next.js 15+ with Turbopack and App Router**. For other setups, see: + +- [Pages Router Setup](/platforms/javascript/guides/nextjs/manual-setup/pages-router/) - For applications using the Pages Router +- [Webpack Setup](/platforms/javascript/guides/nextjs/manual-setup/webpack-setup/) - For applications not using Turbopack + + + +Choose the features you want to configure: + + + +**How this guide works:** + +1. **Install** - Add the Sentry SDK to your project +2. **Configure** - Set up SDK initialization files and Next.js configuration +3. **Verify each feature** - For each feature you enabled, we'll show you how to test it and verify it's working in Sentry + + + +## Install + + + + + + +### Install the Sentry SDK + +Run the command for your preferred package manager to add the Sentry SDK to your application. + + + + +```bash {tabTitle:npm} +npm install @sentry/nextjs --save +``` + +```bash {tabTitle:yarn} +yarn add @sentry/nextjs +``` + +```bash {tabTitle:pnpm} +pnpm add @sentry/nextjs +``` + + + + + + +## Configure + + + + + + +### Apply Instrumentation to Your App + +Extend your app's default Next.js options by adding `withSentryConfig` into your `next.config.ts` file. + + + + +```typescript {filename:next.config.ts} +import type { NextConfig } from "next"; +import { withSentryConfig } from "@sentry/nextjs"; + +const nextConfig: NextConfig = { + // Your existing Next.js configuration +}; + +export default withSentryConfig(nextConfig, { + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + + // Only print logs for uploading source maps in CI + silent: !process.env.CI, + + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, +}); +``` + + + + + + + +### Initialize Sentry SDKs + +Create the following files in your application's root directory (or `src` folder if you have one): + +- `instrumentation-client.ts` - Client-side SDK initialization +- `sentry.server.config.ts` - Server-side SDK initialization +- `sentry.edge.config.ts` - Edge runtime SDK initialization + + + Include your DSN directly in these files, or use a _public_ environment + variable like `NEXT_PUBLIC_SENTRY_DSN`. + + + + + +```typescript {tabTitle:Client} {filename:instrumentation-client.ts} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + // Adds request headers and IP for users + sendDefaultPii: true, + // ___PRODUCT_OPTION_START___ performance + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ performance + integrations: [ + // ___PRODUCT_OPTION_START___ session-replay + Sentry.replayIntegration(), + // ___PRODUCT_OPTION_END___ session-replay + // ___PRODUCT_OPTION_START___ user-feedback + Sentry.feedbackIntegration({ + colorScheme: "system", + }), + // ___PRODUCT_OPTION_END___ user-feedback + ], + // ___PRODUCT_OPTION_START___ session-replay + + // Capture Replay for 10% of all sessions, + // plus for 100% of sessions with an error + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ session-replay + // ___PRODUCT_OPTION_START___ logs + + // Enable logs to be sent to Sentry + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs +}); + +// ___PRODUCT_OPTION_START___ performance +// This export will instrument router navigations +export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; +// ___PRODUCT_OPTION_END___ performance +``` + +```typescript {tabTitle:Server} {filename:sentry.server.config.ts} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + // Adds request headers and IP for users + sendDefaultPii: true, + // ___PRODUCT_OPTION_START___ performance + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ logs + + // Enable logs to be sent to Sentry + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs +}); +``` + +```typescript {tabTitle:Edge} {filename:sentry.edge.config.ts} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + // Adds request headers and IP for users + sendDefaultPii: true, + // ___PRODUCT_OPTION_START___ performance + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ logs + + // Enable logs to be sent to Sentry + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs +}); +``` + + + + + + + +### Register Server-Side SDK + +Create a [Next.js Instrumentation file](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation) named `instrumentation.ts` in your project root (or `src` folder). This file imports your server and edge configurations. + + + + +```typescript {filename: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"); + } +} +``` + + + + + + + +### Capture React Render Errors + +Create `app/global-error.tsx` to capture errors that occur anywhere in your App Router application. + + + + + + + + + + + + +### Capture Server Component Errors + +To capture errors from nested React Server Components, export the `onRequestError` hook from your `instrumentation.ts` file. + + + Requires `@sentry/nextjs` version `8.28.0` or higher and Next.js 15. + + + + + + + + + + + + + +### Server Actions + +Wrap your Server Actions with `Sentry.withServerActionInstrumentation()`. + + + + +```typescript {filename:app/actions.ts} +"use server"; +import * as Sentry from "@sentry/nextjs"; + +export async function submitForm(formData: FormData) { + return Sentry.withServerActionInstrumentation( + "submitForm", // Action name for Sentry + { + recordResponse: true, // Include response data + }, + async () => { + // Your server action logic + const result = await processForm(formData); + return { success: true, data: result }; + } + ); +} +``` + + + + + + + +### Source Maps (Optional) + +Add the `authToken` option to your `next.config.ts` to enable readable stack traces. Set the `SENTRY_AUTH_TOKEN` environment variable in your CI/CD. + + + Keep your auth token secret and out of version control. + + + + + +```typescript {filename:next.config.ts} +export default withSentryConfig(nextConfig, { + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + + // Pass the auth token + authToken: process.env.SENTRY_AUTH_TOKEN, + + // Upload a larger set of source maps for prettier stack traces + widenClientFileUpload: true, +}); +``` + +```sh {filename:.env.local} +SENTRY_AUTH_TOKEN=___ORG_AUTH_TOKEN___ +``` + + + + + + + +### Tunneling (Optional) + +Prevent ad blockers from blocking Sentry events by routing them through your Next.js server. + + + This increases server load. Consider the trade-off for your application. + + + + +If you're using Next.js middleware (`middleware.ts`) or proxy (`proxy.ts`) that intercepts requests, you may need to exclude the tunnel route. When using `tunnelRoute: true`, Sentry generates a random route on each build. For compatibility, use a fixed route instead: + +```typescript {filename:next.config.ts} +export default withSentryConfig(nextConfig, { + tunnelRoute: "/monitoring", +}); +``` + +Then exclude it in your middleware or proxy: + +```typescript {filename:middleware.ts or proxy.ts} +export const config = { + matcher: ["/((?!monitoring|_next/static|_next/image|favicon.ico).*)"], +}; +``` + + + + + + +```typescript {filename:next.config.ts} +export default withSentryConfig(nextConfig, { + // Generate a random route for each build (recommended) + tunnelRoute: true, + + // Or use a fixed route + // tunnelRoute: "/monitoring", +}); +``` + + + + + + +## Error Monitoring + +Test your error monitoring setup by throwing an error and viewing it in Sentry. + + + + + + +### Throw a Test Error + +Add this button to any page and click it to trigger a test error. + + + + + + +```jsx + +``` + + + + + + +### Verify + +Open [**Issues**](https://sentry.io/orgredirect/organizations/:orgslug/issues/) in Sentry to see your test error. [Learn more about capturing errors](/platforms/javascript/guides/nextjs/usage/). + + + +## Session Replay + +Session Replay is already configured in your `instrumentation-client.ts` file. The `replayIntegration()` captures video-like reproductions of user sessions. + + + + + + +### Configuration + +Adjust sample rates in `Sentry.init()`. By default, Sentry masks all DOM text content, images, and user input, giving you heightened confidence that no sensitive data will leave the browser. + +You can customize masking behavior to unmask specific elements or mask additional sensitive content. See [Privacy Configuration](/platforms/javascript/guides/nextjs/session-replay/privacy/) for details. + + + + +```typescript {filename:instrumentation-client.ts} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + integrations: [ + Sentry.replayIntegration({ + // Privacy settings (defaults shown) + maskAllText: true, + maskAllInputs: true, + blockAllMedia: true, + + // Unmask specific elements + unmask: [".public-content"], + }), + ], + + // Sample rates + replaysSessionSampleRate: 0.1, // Capture 10% of all sessions + replaysOnErrorSampleRate: 1.0, // Capture 100% of error sessions +}); +``` + + + + + + +### Verify + +Trigger an error or navigate around your app, then open [**Replays**](https://sentry.io/orgredirect/organizations/:orgslug/replays/) in Sentry to watch the session recording. [Learn more about Session Replay](/platforms/javascript/guides/nextjs/session-replay/). + + + + + +## Tracing + +Tracing is already configured in your SDK initialization files with `tracesSampleRate`. Your Next.js routes and API calls are automatically instrumented. + + + + + + +### Configuration + +Adjust sample rates for production. We recommend starting with a lower rate and increasing as needed. + + + + +```typescript {filename:sentry.server.config.ts} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + // Capture 100% in dev, 10% in production + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, + + // Or use sampling function for more control + tracesSampler: ({ name, parentSampled }) => { + // Always trace errors + if (name.includes("error")) return 1.0; + // Use parent's sampling decision if available + if (parentSampled !== undefined) return parentSampled; + // Default sample rate + return 0.1; + }, +}); +``` + + + + + + + +### Custom Spans + +Add custom spans to trace specific operations in your code. See [Custom Instrumentation](/platforms/javascript/guides/nextjs/tracing/instrumentation/custom-instrumentation/) for more examples. + + + + +```typescript +import * as Sentry from "@sentry/nextjs"; + +// Wrap a function with a span +const result = await Sentry.startSpan( + { name: "expensive-operation", op: "function" }, + async () => { + // Your code here + return await fetchDataFromAPI(); + } +); + +// Or create nested spans +Sentry.startSpan({ name: "parent-operation" }, () => { + Sentry.startSpan({ name: "child-operation" }, () => { + // Nested work + }); +}); +``` + + + + + + +### Verify + +Navigate to any page in your app, then open [**Traces**](https://sentry.io/orgredirect/organizations/:orgslug/explore/traces/) in Sentry to see the performance data. [Learn more about Tracing](/platforms/javascript/guides/nextjs/tracing/). + + + + + +## Logs + +Logs are enabled with `enableLogs: true` in your SDK configuration. Use the Sentry logger to send structured logs. + + + + + + +### Send a Test Log + +Use the Sentry logger to send logs from anywhere in your application. + + + + +```typescript +import * as Sentry from "@sentry/nextjs"; + +// Simple log message +Sentry.logger.info("User clicked checkout button"); + +// Log with parameters +Sentry.logger.info("Order completed", { + orderId: "12345", + total: 99.99, +}); + +// Different log levels +Sentry.logger.debug("Debug information"); +Sentry.logger.warn("Warning message"); +Sentry.logger.error("Error occurred"); +``` + + + + + + +### Verify + +Add a log statement to your code and trigger it, then open [**Logs**](https://sentry.io/orgredirect/organizations/:orgslug/explore/logs/) in Sentry. [Learn more about Logs](/platforms/javascript/guides/nextjs/logs/) or see available [Integrations](/platforms/javascript/guides/nextjs/logs/#integrations) to connect popular logging libraries. + + + + + +## User Feedback + +User Feedback is configured with `feedbackIntegration()` in your `instrumentation-client.ts`. A feedback widget is automatically added to your application. + +### Verify + +Look for the feedback button in your app (usually bottom-right corner) and submit a test feedback. Open [**User Feedback**](https://sentry.io/orgredirect/organizations/:orgslug/feedback/) in Sentry to see the submission. [Learn more about User Feedback](/platforms/javascript/guides/nextjs/user-feedback/). + + + + + +## Hybrid Apps (App Router + Pages Router) + +If your application uses both the App Router and Pages Router: + +1. Follow this guide for App Router components +2. Add a `pages/_error.tsx` file for Pages Router error handling (see [Pages Router Setup](/platforms/javascript/guides/nextjs/manual-setup/pages-router/)) +3. Both routers share the same Sentry configuration files + + + The Sentry SDK automatically detects which router is being used and applies + the appropriate instrumentation. + + +## Next Steps + +You've successfully integrated Sentry into your Next.js application! Here's what to explore next: + +- [Logs Integrations](/platforms/javascript/guides/nextjs/logs/#integrations) - Connect popular logging libraries like Pino, Winston, and Bunyan +- [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/distributed-tracing/) - Trace requests across services and microservices +- [AI Agent Monitoring](/platforms/javascript/guides/nextjs/tracing/instrumentation/ai-agents-module/) - Monitor AI agents built with Vercel AI SDK, LangChain, and more +- [Connect GitHub + Seer](/organization/integrations/source-code-mgmt/github/#installing-github) - Enable AI-powered [root cause analysis](/product/ai-in-sentry/seer/) by connecting your GitHub repository +- [Configuration Options](/platforms/javascript/guides/nextjs/configuration/) - Explore extended SDK configuration options + + + +- Try the [installation wizard](/platforms/javascript/guides/nextjs/) for automatic setup +- [Get support](https://sentry.zendesk.com/hc/en-us/) + + diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx new file mode 100644 index 0000000000000..164643228f27c --- /dev/null +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx @@ -0,0 +1,353 @@ +--- +title: "Pages Router Setup" +sidebar_order: 2 +description: "Manual setup for Next.js applications using the Pages Router." +--- + + + For the fastest setup, we recommend using the [wizard + installer](/platforms/javascript/guides/nextjs). + + +This guide covers manual setup for **Next.js applications using the Pages Router**. For other setups, see: + +- [App Router Setup](/platforms/javascript/guides/nextjs/manual-setup/) - For applications using the App Router (Next.js 15+) +- [Webpack Setup](/platforms/javascript/guides/nextjs/manual-setup/webpack-setup/) - For applications not using Turbopack + + + + + +## Install + + + + + + +### Install the Sentry SDK + +Run the command for your preferred package manager to add the Sentry SDK to your application. + + + + +```bash {tabTitle:npm} +npm install @sentry/nextjs --save +``` + +```bash {tabTitle:yarn} +yarn add @sentry/nextjs +``` + +```bash {tabTitle:pnpm} +pnpm add @sentry/nextjs +``` + + + + + + +## Configure + + + + + + +### Apply Instrumentation to Your App + +Extend your app's default Next.js options by adding `withSentryConfig` into your `next.config.ts` (or `next.config.js`) file. + + + + +```typescript {filename:next.config.ts} +import type { NextConfig } from "next"; +import { withSentryConfig } from "@sentry/nextjs"; + +const nextConfig: NextConfig = { + // Your existing Next.js configuration +}; + +export default withSentryConfig(nextConfig, { + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + + // Only print logs for uploading source maps in CI + silent: !process.env.CI, + + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, +}); +``` + + + + + + + +### Initialize Sentry SDKs + +Create the following files in your application's root directory (or `src` folder if you have one): + +- `sentry.client.config.ts` - Client-side SDK initialization +- `sentry.server.config.ts` - Server-side SDK initialization +- `sentry.edge.config.ts` - Edge runtime SDK initialization (if using edge routes) + + + Include your DSN directly in these files, or use a _public_ environment + variable like `NEXT_PUBLIC_SENTRY_DSN`. + + + + + +```typescript {tabTitle:Client} {filename:sentry.client.config.ts} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, + + // Capture Replay for 10% of all sessions, + // plus for 100% of sessions with an error + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + + integrations: [Sentry.replayIntegration()], +}); +``` + +```typescript {tabTitle:Server} {filename:sentry.server.config.ts} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, +}); +``` + +```typescript {tabTitle:Edge} {filename:sentry.edge.config.ts} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, +}); +``` + + + + + + + +### Capture Pages Router Errors + +Create or update `pages/_error.tsx` to capture errors that occur during server-side rendering or in page components. + +The `getInitialProps` method captures the error asynchronously, ensuring Sentry has time to send the error before serverless functions terminate. + + + + + + + + + + + + +### Source Maps (Optional) + +Add the `authToken` option to your `next.config.ts` to enable readable stack traces. Set the `SENTRY_AUTH_TOKEN` environment variable in your CI/CD. + + + Keep your auth token secret and out of version control. + + + + + +```typescript {filename:next.config.ts} +export default withSentryConfig(nextConfig, { + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + + // Pass the auth token + authToken: process.env.SENTRY_AUTH_TOKEN, + + // Upload a larger set of source maps for prettier stack traces + widenClientFileUpload: true, +}); +``` + +```sh {filename:.env.local} +SENTRY_AUTH_TOKEN=___ORG_AUTH_TOKEN___ +``` + + + + + + +## Error Monitoring + +Test your error monitoring setup by throwing an error and viewing it in Sentry. + + + + + + +### Throw a Test Error + +Add this button to any page and click it to trigger a test error. + + + + +```tsx {filename:pages/index.tsx} +export default function Home() { + return ( + + ); +} +``` + + + + + + +### Verify + +Open [**Issues**](https://sentry.io/orgredirect/organizations/:orgslug/issues/) in Sentry to see your test error. [Learn more about capturing errors](/platforms/javascript/guides/nextjs/usage/). + +## Manually Capture Exceptions + + + When using Webpack, Pages Router API routes can be auto-instrumented with the + `autoInstrumentServerFunctions` option. See [Webpack + Setup](/platforms/javascript/guides/nextjs/manual-setup/webpack-setup/) for + details. + + + + + + + +### Pages Router API Routes + +For manual error capturing in API routes, use `Sentry.captureException()`. + + + + +```typescript {filename:pages/api/example.ts} +import * as Sentry from "@sentry/nextjs"; +import type { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + try { + // ...might throw + res.status(200).json({ ok: true }); + } catch (err) { + Sentry.captureException(err); + res.status(500).json({ error: "Internal Server Error" }); + } +} +``` + + + + + + +## Vercel Cron Jobs (Optional) + +Automatically create [Cron Monitors](/product/crons/) in Sentry for your [Vercel cron jobs](https://vercel.com/docs/cron-jobs). + + + + + + +### Enable Automatic Cron Monitoring + +Add the `automaticVercelMonitors` option to your `next.config.ts`. + + + Automatic Vercel cron jobs instrumentation currently only supports Pages + Router API routes. App Router route handlers are not yet supported. + + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + automaticVercelMonitors: true, +}); +``` + + + + + + + + +## Hybrid Apps (App Router + Pages Router) + +If your application uses both the App Router and Pages Router: + +1. Follow the [App Router Setup](/platforms/javascript/guides/nextjs/manual-setup/) for App Router components +2. Add the `pages/_error.tsx` file from this guide for Pages Router error handling +3. Both routers share the same Sentry configuration files + + + The Sentry SDK automatically detects which router is being used and applies + the appropriate instrumentation. + + +## Next Steps + +- [Logs Integrations](/platforms/javascript/guides/nextjs/logs/#integrations) - Connect popular logging libraries like Pino, Winston, and Bunyan +- [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/distributed-tracing/) - Trace requests across services and microservices +- [AI Agent Monitoring](/platforms/javascript/guides/nextjs/tracing/instrumentation/ai-agents-module/) - Monitor AI agents built with Vercel AI SDK, LangChain, and more +- [Connect GitHub + Seer](/organization/integrations/source-code-mgmt/github/#installing-github) - Enable AI-powered [root cause analysis](/product/ai-in-sentry/seer/) +- [Configuration Options](/platforms/javascript/guides/nextjs/configuration/) - Explore extended SDK configuration + + + +- Try the [installation wizard](/platforms/javascript/guides/nextjs/) for automatic setup +- [Get support](https://sentry.zendesk.com/hc/en-us/) + + diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx new file mode 100644 index 0000000000000..be37dfe60d5cc --- /dev/null +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx @@ -0,0 +1,315 @@ +--- +title: "Webpack Setup" +sidebar_order: 3 +description: "Additional configuration for Next.js applications using Webpack instead of Turbopack." +--- + +This guide covers the configuration differences for **Next.js applications using Webpack** (the default bundler before Next.js 15). Complete the [main manual setup](/platforms/javascript/guides/nextjs/manual-setup/) first, then apply these Webpack-specific configurations. + + + If you're using Next.js 15+ with Turbopack (the default), you don't need this + guide. See the [main manual + setup](/platforms/javascript/guides/nextjs/manual-setup/) instead. + + +## Key Differences: Webpack vs Turbopack + +| Feature | Turbopack | Webpack | +| ------------------------------- | ------------------------------- | ----------------------------------- | +| Server function instrumentation | Automatic via Next.js telemetry | Build-time code injection | +| Middleware instrumentation | Automatic via Next.js telemetry | Build-time code injection | +| Source map upload | Post-build via CLI (default) | During build via plugin (default) | +| Route exclusion | Not supported | Supported via `excludeServerRoutes` | + +## Auto-Instrumentation Options + +With Webpack, Sentry automatically instruments your server functions, middleware, and app directory components at build time. You can control this behavior: + + + + + + +### Configure Auto-Instrumentation + +These options are enabled by default with Webpack. Disable them if you prefer manual instrumentation or experience build issues. + +**Note**: These options instrument Pages Router pages, API routes, and App Router components, but do NOT automatically instrument Server Actions. Server Actions require manual wrapping using `withServerActionInstrumentation()`. + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + // Instrument Pages Router API routes and data fetching methods (default: true) + autoInstrumentServerFunctions: true, + + // Instrument Next.js middleware (default: true) + autoInstrumentMiddleware: true, + + // Instrument App Router components (default: true) + autoInstrumentAppDirectory: true, +}); +``` + + + + + + +## Exclude Routes from Instrumentation + +With Webpack, you can exclude specific routes from automatic instrumentation. This is useful for health check endpoints or routes that shouldn't be monitored. + + + This option has no effect when using Turbopack, as Turbopack relies on Next.js + telemetry features instead of build-time instrumentation. + + + + + + + +### Configure Route Exclusions + +Specify routes as URL paths (not file system paths). Routes must have a leading slash and no trailing slash. + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + excludeServerRoutes: [ + "/api/health", + "/api/excluded/[parameter]", + /^\/internal\//, // Regex for all /internal/* routes + ], +}); +``` + + + + + + +## Source Map Upload + +By default, Webpack uploads source maps **during the build process** using the Sentry Webpack Plugin. This happens separately for each build (client, server, edge), which can increase build time. + +For faster builds, you can use the post-build upload mode (available in Next.js 15.4.1+), which uploads all source maps in a single operation after all builds complete. + + + + + + +### Default: Upload During Build + +The Sentry Webpack Plugin runs during each webpack compilation and uploads source maps as they're generated. + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + authToken: process.env.SENTRY_AUTH_TOKEN, + + // Default webpack behavior - uploads during build + useRunAfterProductionCompileHook: false, +}); +``` + + + + + + + + + + + +### Option: Upload After Build (Next.js 15.4.1+) + +Enable post-build upload for faster builds. All source maps are uploaded once after all webpack builds complete. + + + This option requires Next.js 15.4.1 or later. Turbopack always uses this mode. + + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + authToken: process.env.SENTRY_AUTH_TOKEN, + + // Upload after all builds complete (faster) + useRunAfterProductionCompileHook: true, +}); +``` + + + + + + + + + + + +### Advanced Webpack Plugin Options + +Pass options directly to the underlying Sentry Webpack Plugin for advanced configuration. + + + The `unstable_sentryWebpackPluginOptions` API may change in future releases. + + + + These options only apply when `useRunAfterProductionCompileHook` is `false` + (the default). + + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + authToken: process.env.SENTRY_AUTH_TOKEN, + + // Advanced Webpack plugin options + unstable_sentryWebpackPluginOptions: { + sourcemaps: { + assets: ["./build/**/*.js", "./build/**/*.map"], + ignore: ["node_modules/**"], + }, + }, +}); +``` + + + + + + +## Server Actions with Webpack + +Server Actions (functions marked with `"use server"`) are **not automatically instrumented** by Webpack's build-time instrumentation. You must manually wrap them for error and performance monitoring. + + + + + + +### Manual Server Action Instrumentation + +Wrap Server Actions with `withServerActionInstrumentation()` to capture errors and performance data. + + + + +```typescript {filename:app/actions.ts} +"use server"; +import * as Sentry from "@sentry/nextjs"; + +export async function submitForm(formData: FormData) { + return Sentry.withServerActionInstrumentation( + "submitForm", // Action name for Sentry + { + recordResponse: true, // Include response data + }, + async () => { + // Your server action logic + const result = await processForm(formData); + return { success: true, data: result }; + } + ); +} +``` + + + + + + +## Tunneling with Webpack + +Tunneling works identically for both Webpack and Turbopack. Sentry automatically filters tunnel requests from middleware spans to prevent noise in your monitoring data. + + + + + + +### Configure Tunnel Route + +Enable tunneling to bypass ad-blockers. Sentry automatically handles middleware span filtering for tunnel requests. + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + // Auto-generated random route (recommended) + tunnelRoute: true, + + // Or use a fixed route + // tunnelRoute: "/monitoring", +}); +``` + + + + + + +## Migrating from Webpack to Turbopack + +If you're upgrading to Turbopack: + +1. **Remove webpack-only options** - `excludeServerRoutes` and `unstable_sentryWebpackPluginOptions` have no effect with Turbopack +2. **Understand source map changes** - Turbopack always uses post-build upload (no plugin-based upload option) +3. **Test auto-instrumentation** - Turbopack uses Next.js telemetry instead of build-time injection; verify your monitoring still works + +```typescript {filename:next.config.ts} +// Before (Webpack) +export default withSentryConfig(nextConfig, { + excludeServerRoutes: ["/api/health"], + tunnelRoute: true, +}); + +// After (Turbopack) +export default withSentryConfig(nextConfig, { + // excludeServerRoutes is not supported with Turbopack + tunnelRoute: "/monitoring", +}); +``` + +## Next Steps + +- [Logs Integrations](/platforms/javascript/guides/nextjs/logs/#integrations) - Connect popular logging libraries like Pino, Winston, and Bunyan +- [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/distributed-tracing/) - Trace requests across services and microservices +- [AI Agent Monitoring](/platforms/javascript/guides/nextjs/tracing/instrumentation/ai-agents-module/) - Monitor AI agents built with Vercel AI SDK, LangChain, and more +- [Connect GitHub + Seer](/organization/integrations/source-code-mgmt/github/#installing-github) - Enable AI-powered [root cause analysis](/product/ai-in-sentry/seer/) +- [Configuration Options](/platforms/javascript/guides/nextjs/configuration/) - Explore extended SDK configuration diff --git a/platform-includes/getting-started-capture-errors/nextjs/app-router-global-error/javascript.nextjs.mdx b/platform-includes/getting-started-capture-errors/nextjs/app-router-global-error/javascript.nextjs.mdx new file mode 100644 index 0000000000000..8a5c016e19ebe --- /dev/null +++ b/platform-includes/getting-started-capture-errors/nextjs/app-router-global-error/javascript.nextjs.mdx @@ -0,0 +1,52 @@ +```tsx {filename:global-error.tsx} +"use client"; + +import * as Sentry from "@sentry/nextjs"; +import NextError from "next/error"; +import { useEffect } from "react"; + +export default function GlobalError({ + error, +}: { + error: Error & { digest?: string }; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + + {/* `NextError` is the default Next.js error page component. Its type + definition requires a `statusCode` prop. However, since the App Router + does not expose status codes for errors, we simply pass 0 to render a + generic error message. */} + + + + ); +} +``` + +```jsx {filename:global-error.jsx} +"use client"; + +import * as Sentry from "@sentry/nextjs"; +import NextError from "next/error"; +import { useEffect } from "react"; + +export default function GlobalError({ error }) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + + {/* This is the default Next.js error component. */} + + + + ); +} +``` diff --git a/platform-includes/getting-started-capture-errors/nextjs/nested-server-components/javascript.nextjs.mdx b/platform-includes/getting-started-capture-errors/nextjs/nested-server-components/javascript.nextjs.mdx new file mode 100644 index 0000000000000..5e6e00a8ade38 --- /dev/null +++ b/platform-includes/getting-started-capture-errors/nextjs/nested-server-components/javascript.nextjs.mdx @@ -0,0 +1,31 @@ +```JavaScript {filename:instrumentation.ts|js} +import * as Sentry from "@sentry/nextjs"; + +export const onRequestError = Sentry.captureRequestError; +``` + + +You can call `captureRequestError` with all arguments passed to `onRequestError`: + +```TypeScript {filename:instrumentation.ts} +import * as Sentry from "@sentry/nextjs"; +import type { Instrumentation } from "next"; + +export const onRequestError: Instrumentation.onRequestError = (...args) => { + Sentry.captureRequestError(...args); + + // ... additional logic here +}; +``` + +```JavaScript {filename:instrumentation.js} +import * as Sentry from "@sentry/nextjs"; + +export const onRequestError = (...args) => { + Sentry.captureRequestError(...args); + + // ... additional logic here +}; +``` + + diff --git a/platform-includes/getting-started-capture-errors/nextjs/pages-router-error/javascript.nextjs.mdx b/platform-includes/getting-started-capture-errors/nextjs/pages-router-error/javascript.nextjs.mdx new file mode 100644 index 0000000000000..7f2583720767b --- /dev/null +++ b/platform-includes/getting-started-capture-errors/nextjs/pages-router-error/javascript.nextjs.mdx @@ -0,0 +1,41 @@ +```tsx {filename:_error.tsx} +import * as Sentry from "@sentry/nextjs"; +import type { NextPage } from "next"; +import type { ErrorProps } from "next/error"; +import Error from "next/error"; + +const CustomErrorComponent: NextPage = (props) => { + return ; +}; + +CustomErrorComponent.getInitialProps = async (contextData) => { + // In case this is running in a serverless function, await this in order to give Sentry + // time to send the error before the lambda exits + await Sentry.captureUnderscoreErrorException(contextData); + + // This will contain the status code of the response + return Error.getInitialProps(contextData); +}; + +export default CustomErrorComponent; +``` + +```jsx {filename:_error.jsx} +import * as Sentry from "@sentry/nextjs"; +import Error from "next/error"; + +const CustomErrorComponent = (props) => { + return ; +}; + +CustomErrorComponent.getInitialProps = async (contextData) => { + // In case this is running in a serverless function, await this in order to give Sentry + // time to send the error before the lambda exits + await Sentry.captureUnderscoreErrorException(contextData); + + // This will contain the status code of the response + return Error.getInitialProps(contextData); +}; + +export default CustomErrorComponent; +``` diff --git a/platform-includes/getting-started-prerequisites/javascript.nextjs.mdx b/platform-includes/getting-started-prerequisites/javascript.nextjs.mdx new file mode 100644 index 0000000000000..bcb8b05780126 --- /dev/null +++ b/platform-includes/getting-started-prerequisites/javascript.nextjs.mdx @@ -0,0 +1,6 @@ +## Prerequisites + +You need: + +- A Next.js application +- A Sentry [account](https://sentry.io/signup/) and [project](/product/projects/) diff --git a/src/components/splitLayout/README.md b/src/components/splitLayout/README.md index 049fefcbbcf1e..9612599cd1e75 100644 --- a/src/components/splitLayout/README.md +++ b/src/components/splitLayout/README.md @@ -92,4 +92,4 @@ The code section uses `position: sticky` on desktop to keep code visible while s See it in action: -- [Next.js Getting Started - Essential Configuration](/platforms/javascript/guides/nextjs/getting-started/#essential-configuration) +- [Next.js Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx new file mode 100644 index 0000000000000..3c05b569f241d --- /dev/null +++ b/src/components/verificationChecklist/index.tsx @@ -0,0 +1,521 @@ +'use client'; + +import { + Children, + isValidElement, + ReactElement, + ReactNode, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import {ArrowRightIcon, CheckIcon, ChevronDownIcon} from '@radix-ui/react-icons'; + +import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent'; + +import styles from './style.module.scss'; + +type ChecklistItemProps = { + id: string; + label: string; + children?: ReactNode; + description?: string; + /** Secondary link (usually to docs) */ + docsLink?: string; + docsLinkText?: string; + /** Primary link (usually to Sentry UI) */ + link?: string; + linkText?: string; + /** Onboarding option ID - shows "(Optional)" when this option is unchecked */ + optionId?: string; +}; + +// Marker symbol to identify ChecklistItem components +const CHECKLIST_ITEM_MARKER = Symbol.for('sentry-docs-checklist-item'); + +// Individual checklist item component for use as children +// Note: This component doesn't render anything itself - it's used as a container +// for props/children that VerificationChecklist extracts and renders +function ChecklistItemComponent(_props: ChecklistItemProps) { + // Return null - VerificationChecklist extracts props and renders items itself + return null; +} +ChecklistItemComponent.displayName = 'ChecklistItem'; +// Static marker for identification +( + ChecklistItemComponent as unknown as {__checklistItemMarker: symbol} +).__checklistItemMarker = CHECKLIST_ITEM_MARKER; + +export const ChecklistItem = ChecklistItemComponent; + +type Props = { + /** Unique identifier for this checklist (used for localStorage) */ + checklistId?: string; + children?: ReactNode; + /** Enable collapsible items (default: true) */ + collapsible?: boolean; + /** Auto-expand next item when current is checked (default: true) */ + sequential?: boolean; + /** Troubleshooting link URL */ + troubleshootingLink?: string; +}; + +function getStorageKey(checklistId: string): string { + return `sentry-docs-checklist-${checklistId}`; +} + +export function VerificationChecklist({ + children, + checklistId = 'default', + collapsible = true, + sequential = true, + troubleshootingLink = '/platforms/javascript/guides/nextjs/troubleshooting/', +}: Props) { + const [checkedItems, setCheckedItems] = useState>({}); + const [expandedItems, setExpandedItems] = useState>({}); + const [mounted, setMounted] = useState(false); + const [optionalItemIds, setOptionalItemIds] = useState>(new Set()); + const listRef = useRef(null); + const {emit} = usePlausibleEvent(); + + // Helper to check if an element is a ChecklistItem + // MDX may transform the component type, so we check multiple ways + const isChecklistItemElement = useCallback((child: ReactElement): boolean => { + const type = child.type; + const props = child.props as Record; + + // Check for our static marker (most reliable) + if (typeof type === 'function') { + const funcType = type as { + __checklistItemMarker?: symbol; + displayName?: string; + name?: string; + }; + if (funcType.__checklistItemMarker === CHECKLIST_ITEM_MARKER) { + return true; + } + if (funcType.displayName === 'ChecklistItem' || funcType.name === 'ChecklistItem') { + return true; + } + } + + // Direct reference check + if (type === ChecklistItem) { + return true; + } + + // MDX might set mdxType or originalType + if (props.mdxType === 'ChecklistItem' || props.originalType === ChecklistItem) { + return true; + } + + // Last resort: check if props match ChecklistItem structure and it's not a DOM element + if ( + typeof type !== 'string' && + typeof props.id === 'string' && + typeof props.label === 'string' + ) { + return true; + } + + return false; + }, []); + + // Extract items from children - memoize to avoid infinite loops + // MDX may wrap children in fragments, so we need to deeply traverse + const items = useMemo(() => { + const extracted: ChecklistItemProps[] = []; + + const processChild = (child: ReactNode) => { + if (!isValidElement(child)) { + return; + } + + // Check if this is a ChecklistItem + if (isChecklistItemElement(child)) { + const props = child.props as ChecklistItemProps; + if (props.id && props.label) { + extracted.push(props); + } + return; + } + + // If it's a fragment or wrapper, recurse into its children + const childProps = child.props as {children?: ReactNode}; + if (childProps.children) { + Children.forEach(childProps.children, processChild); + } + }; + + Children.forEach(children, processChild); + return extracted; + }, [children, isChecklistItemElement]); + + // Get the first item ID for initial expanded state + const firstItemId = items.length > 0 ? items[0].id : null; + + // Load checked items from localStorage on mount and set initial expanded state + useEffect(() => { + setMounted(true); + try { + const stored = localStorage.getItem(getStorageKey(checklistId)); + if (stored) { + setCheckedItems(JSON.parse(stored)); + } + } catch { + // Ignore localStorage errors + } + + // If collapsible, expand the first item by default + if (collapsible && firstItemId) { + setExpandedItems({[firstItemId]: true}); + } + }, [checklistId, collapsible, firstItemId]); + + // Save to localStorage when checked items change + useEffect(() => { + if (!mounted) { + return; + } + try { + localStorage.setItem(getStorageKey(checklistId), JSON.stringify(checkedItems)); + } catch { + // Ignore localStorage errors + } + }, [checkedItems, checklistId, mounted]); + + // Watch for which onboarding options are enabled/disabled + // We watch the OnboardingOptionButtons checkboxes to determine which options are enabled + useEffect(() => { + if (!mounted) { + return undefined; + } + + const updateOptionalItems = () => { + const newOptionalIds = new Set(); + const onboardingContainer = document.querySelector('.onboarding-options'); + + items.forEach(item => { + if (item.optionId && onboardingContainer) { + // Find all Radix checkboxes in the onboarding options + // The checkbox has data-state="checked" or data-state="unchecked" + const checkboxes = onboardingContainer.querySelectorAll( + 'button[role="checkbox"]' + ); + + // Map option IDs to their display names + const optionNameMap: Record = { + performance: 'Tracing', + 'session-replay': 'Session Replay', + logs: 'Logs', + 'error-monitoring': 'Error Monitoring', + }; + + const optionName = optionNameMap[item.optionId]; + if (!optionName) { + return; + } + + // Find the checkbox whose parent label contains the option name + let isChecked = false; + checkboxes.forEach(checkbox => { + const label = checkbox.closest('label'); + if (label && label.textContent?.includes(optionName)) { + isChecked = checkbox.getAttribute('data-state') === 'checked'; + } + }); + + if (!isChecked) { + newOptionalIds.add(item.id); + } + } + }); + setOptionalItemIds(newOptionalIds); + }; + + // Initial check after a short delay to let onboarding buttons render + const initialTimeout = setTimeout(updateOptionalItems, 100); + + // Set up MutationObserver to watch for checkbox state changes + const observer = new MutationObserver(() => { + updateOptionalItems(); + }); + + // Watch the document for data-state attribute changes on checkboxes + observer.observe(document.body, { + attributes: true, + attributeFilter: ['data-state'], + subtree: true, + }); + + // Also listen for click events on the onboarding buttons container + const handleClick = (e: Event) => { + const target = e.target as HTMLElement; + if (target.closest('.onboarding-options')) { + // Delay to let checkbox state update + setTimeout(updateOptionalItems, 50); + } + }; + document.addEventListener('click', handleClick); + + return () => { + clearTimeout(initialTimeout); + observer.disconnect(); + document.removeEventListener('click', handleClick); + }; + }, [items, mounted]); + + // All items are always visible, but some are marked as optional + const completedCount = items.filter(item => checkedItems[item.id]).length; + const totalCount = items.length; + const allComplete = completedCount === totalCount && totalCount > 0; + + const toggleItem = useCallback( + (itemId: string, itemLabel: string) => { + setCheckedItems(prev => { + const newChecked = !prev[itemId]; + const newState = {...prev, [itemId]: newChecked}; + + // Emit event for checking/unchecking item + emit('Checklist Item Toggle', { + props: { + checked: newChecked, + checklistId, + itemId, + itemLabel, + page: window.location.pathname, + }, + }); + + // If sequential and checking an item, expand the next unchecked item + if (sequential && newChecked) { + const currentIndex = items.findIndex(item => item.id === itemId); + if (currentIndex !== -1 && currentIndex < items.length - 1) { + // Find the next unchecked item + for (let i = currentIndex + 1; i < items.length; i++) { + if (!newState[items[i].id]) { + setExpandedItems(prevExpanded => ({ + ...prevExpanded, + [itemId]: false, // Collapse current + [items[i].id]: true, // Expand next + })); + break; + } + } + } + } + + // Check if all items are now complete + const newCompletedCount = items.filter(item => newState[item.id]).length; + if (newCompletedCount === items.length && newChecked) { + emit('Checklist Complete', { + props: { + checklistId, + page: window.location.pathname, + }, + }); + } + + return newState; + }); + }, + [checklistId, emit, items, sequential] + ); + + const toggleExpanded = useCallback((itemId: string) => { + setExpandedItems(prev => ({...prev, [itemId]: !prev[itemId]})); + }, []); + + const handleLinkClick = useCallback( + (itemId: string, linkText: string, link: string) => { + emit('Checklist Link Click', { + props: { + checklistId, + itemId, + link, + linkText, + page: window.location.pathname, + }, + }); + }, + [checklistId, emit] + ); + + // Get children content for each item + const getItemChildren = (itemId: string): ReactNode => { + let itemChildren: ReactNode = null; + + const processChild = (child: ReactNode) => { + if (!isValidElement(child) || itemChildren !== null) { + return; + } + + if (isChecklistItemElement(child)) { + const props = child.props as ChecklistItemProps; + if (props.id === itemId) { + itemChildren = props.children; + } + return; + } + + // If it's a fragment or wrapper, recurse into its children + const childProps = child.props as {children?: ReactNode}; + if (childProps.children) { + Children.forEach(childProps.children, processChild); + } + }; + + Children.forEach(children, processChild); + return itemChildren; + }; + + return ( +
+
+
+
0 ? (completedCount / totalCount) * 100 : 0}%`, + }} + /> +
+ + {completedCount} of {totalCount} complete + +
+ +
    + {items.map(item => { + const isChecked = checkedItems[item.id] || false; + const isExpanded = collapsible ? expandedItems[item.id] || false : true; + const itemChildren = getItemChildren(item.id); + const hasContent = !!itemChildren || !!item.description || !!item.link; + const isOptional = optionalItemIds.has(item.id); + + return ( +
  • +
    toggleExpanded(item.id) : undefined} + style={collapsible ? {cursor: 'pointer'} : undefined} + > +
    + + { + e.stopPropagation(); + toggleItem(item.id, item.label); + }} + onClick={e => e.stopPropagation()} + className={styles.checkbox} + /> + { + e.stopPropagation(); + toggleItem(item.id, item.label); + }} + > + {isChecked && } + + + + + {item.label} + {isOptional && ( + (Optional) + )} + + +
    +
    + {item.docsLink && ( + { + e.stopPropagation(); + handleLinkClick( + item.id, + item.docsLinkText || 'Learn more', + item.docsLink! + ); + }} + > + {item.docsLinkText || 'Learn more'} + + )} + {collapsible && ( + + )} +
    +
    + {isExpanded && hasContent && ( +
    + {item.description && ( +

    {item.description}

    + )} + {item.link && ( + + handleLinkClick(item.id, item.linkText || 'Open', item.link!) + } + > + {item.linkText || 'Open'} + + + )} + {itemChildren && ( +
    {itemChildren}
    + )} +
    + )} +
  • + ); + })} +
+ + {allComplete && ( +
+ + All done! Sentry is successfully configured. +
+ )} + +
+ Something not working? Check troubleshooting + {' ยท '} + Get support +
+
+ ); +} diff --git a/src/components/verificationChecklist/style.module.scss b/src/components/verificationChecklist/style.module.scss new file mode 100644 index 0000000000000..9cc6164d72c78 --- /dev/null +++ b/src/components/verificationChecklist/style.module.scss @@ -0,0 +1,449 @@ +.checklist { + border: 1px solid var(--gray-200); + border-radius: 0.5rem; + padding: 1.25rem; + margin: 1rem 0; + background: var(--gray-50); +} + +.progress { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; +} + +.progressBar { + flex: 1; + height: 8px; + background: var(--gray-200); + border-radius: 4px; + overflow: hidden; +} + +.progressFill { + height: 100%; + background: linear-gradient(90deg, #6c5fc7 0%, #8b7fd9 100%); + border-radius: 4px; + transition: width 0.3s ease; +} + +.progressText { + font-size: 0.875rem; + color: var(--gray-600); + white-space: nowrap; +} + +.items { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.item { + border-radius: 0.375rem; + background: var(--white); + border: 1px solid var(--gray-100); + transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease; + overflow: hidden; + + &:hover { + border-color: var(--gray-200); + } + + &.expanded { + border-color: var(--gray-200); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + } + + &.collapsed { + .itemHeader { + padding: 0.75rem; + } + } +} + +.itemHeader { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem; + user-select: none; + + @media (max-width: 640px) { + flex-wrap: wrap; + gap: 0.5rem; + } +} + +.headerLeft { + display: flex; + align-items: flex-start; + gap: 0.75rem; + flex: 1; + min-width: 0; +} + +.headerRight { + display: flex; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; + + @media (max-width: 640px) { + margin-left: auto; + } +} + +.checkboxWrapper { + position: relative; + flex-shrink: 0; + margin-top: 2px; +} + +.checkbox { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.customCheckbox { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border: 2px solid var(--gray-300); + border-radius: 4px; + background: var(--white); + transition: all 0.15s ease; + cursor: pointer; + + &:hover { + border-color: #6c5fc7; + } + + &.checked { + background: #6c5fc7; + border-color: #6c5fc7; + } +} + +.checkIcon { + width: 14px; + height: 14px; + color: white; +} + +.labelContainer { + display: flex; + flex-direction: column; + gap: 0.125rem; + min-width: 0; + flex: 1; +} + +.labelText { + font-weight: 500; + color: var(--gray-900); + transition: color 0.15s ease; + display: inline-flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; + + &.checked { + text-decoration: line-through; + color: var(--gray-500); + } +} + +.optionalBadge { + font-size: 0.75rem; + font-weight: 400; + color: var(--gray-400); + background: var(--gray-100); + padding: 0.125rem 0.375rem; + border-radius: 4px; +} + +.docsLink { + font-size: 0.8125rem; + color: var(--gray-500); + text-decoration: none; + white-space: nowrap; + + &:hover { + text-decoration: underline; + color: var(--gray-600); + } +} + +.expandButton { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border: 1px solid var(--gray-200); + border-radius: 4px; + background: var(--white); + cursor: pointer; + transition: all 0.15s ease; + + &:hover { + background: var(--gray-50); + border-color: var(--gray-300); + } + + &.expanded { + .chevronIcon { + transform: rotate(180deg); + } + } +} + +.chevronIcon { + width: 16px; + height: 16px; + color: var(--gray-500); + transition: transform 0.2s ease; +} + +.expandedContent { + padding: 0 0.75rem 0.75rem 2.75rem; + animation: slideDown 0.2s ease; + + // Handle MDX content directly in expanded area + > :global(div:first-child), + > :global(pre:first-child) { + margin-top: 0.5rem; + } + + // Paragraphs before code blocks + :global(p + pre), + :global(p + div) { + margin-top: 0.75rem; + } + + // Remove extra margin from last element + > :global(*:last-child) { + margin-bottom: 0; + } + + // Code tabs container + :global([class*="codeContext"]) { + margin-top: 0.5rem; + } +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.expandedDescription { + font-size: 0.875rem; + color: var(--gray-600); + margin: 0 0 0.75rem 0; + line-height: 1.5; +} + +.primaryLink { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: 0.875rem; + font-weight: 500; + color: #6c5fc7; + text-decoration: none; + margin-bottom: 0.75rem; + + &:hover { + text-decoration: underline; + } +} + +.arrowIcon { + width: 14px; + height: 14px; +} + +.itemContent { + margin-top: 0.75rem; + + // Reset some MDX component styles within content + > :global(p:first-child) { + margin-top: 0; + } + + > :global(*:last-child) { + margin-bottom: 0; + } + + // Code blocks get proper spacing + :global(pre) { + margin: 0.5rem 0; + + &:last-child { + margin-bottom: 0; + } + } +} + +.successMessage { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 1rem; + padding: 0.75rem 1rem; + background: #d3f9d8; + border: 1px solid #69db7c; + border-radius: 0.375rem; + color: #2b8a3e; + font-weight: 500; +} + +.successIcon { + width: 18px; + height: 18px; +} + +.troubleshooting { + margin-top: 1rem; + padding-top: 0.75rem; + border-top: 1px solid var(--gray-200); + font-size: 0.875rem; + color: var(--gray-500); + text-align: center; + + a { + color: #6c5fc7; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } +} + +// Dark mode support +:global(.dark) { + .checklist { + background: #1a1523; + border-color: #3e3446; + } + + .item { + background: #231c3d; + border-color: #3e3446; + + &:hover { + border-color: #584774; + } + + &.expanded { + border-color: #584774; + } + } + + .customCheckbox { + background: #231c3d; + border-color: #584774; + + &:hover { + border-color: #8b7fd9; + } + + &.checked { + background: #8b7fd9; + border-color: #8b7fd9; + } + } + + .labelText { + color: #e2d9e9; + + &.checked { + color: #9481a4; + } + } + + .optionalBadge { + background: #3e3446; + color: #9481a4; + } + + .expandedDescription { + color: #bbadc6; + } + + .progressBar { + background: #3e3446; + } + + .progressText { + color: #bbadc6; + } + + .primaryLink { + color: #a796f0; + + &:hover { + color: #c4baff; + } + } + + .docsLink { + color: #9481a4; + + &:hover { + color: #bbadc6; + } + } + + .expandButton { + background: #231c3d; + border-color: #3e3446; + + &:hover { + background: #2d2447; + border-color: #584774; + } + } + + .chevronIcon { + color: #9481a4; + } + + .successMessage { + background: rgba(45, 106, 79, 0.3); + border-color: #40916c; + color: #69db7c; + } + + .troubleshooting { + border-color: #3e3446; + color: #9481a4; + + a { + color: #a796f0; + + &:hover { + color: #c4baff; + } + } + } +} diff --git a/src/hooks/usePlausibleEvent.tsx b/src/hooks/usePlausibleEvent.tsx index a899f5426facb..13ec4829068be 100644 --- a/src/hooks/usePlausibleEvent.tsx +++ b/src/hooks/usePlausibleEvent.tsx @@ -7,6 +7,24 @@ type PlausibleEventProps = { ['Ask AI Referrer']: { referrer: string; }; + ['Checklist Complete']: { + checklistId: string; + page: string; + }; + ['Checklist Item Toggle']: { + checked: boolean; + checklistId: string; + itemId: string; + itemLabel: string; + page: string; + }; + ['Checklist Link Click']: { + checklistId: string; + itemId: string; + link: string; + linkText: string; + page: string; + }; ['Copy Expandable Content']: { page: string; title: string; diff --git a/src/mdxComponents.ts b/src/mdxComponents.ts index c6458a492276c..e09e52ef3a521 100644 --- a/src/mdxComponents.ts +++ b/src/mdxComponents.ts @@ -55,6 +55,7 @@ import { } from './components/splitLayout'; import {StepComponent, StepConnector} from './components/stepConnector'; import {TableOfContents} from './components/tableOfContents'; +import {ChecklistItem, VerificationChecklist} from './components/verificationChecklist'; import {VersionRequirement} from './components/version-requirement'; import {VimeoEmbed} from './components/video'; @@ -116,6 +117,8 @@ export function mdxComponents( SplitSectionCode, StepComponent, StepConnector, + VerificationChecklist, + ChecklistItem, VimeoEmbed, VersionRequirement, a: SmartLink,