From 65dbcd2d2cd73aa87c815a2e73b8993ed47ffb1c Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Tue, 9 Dec 2025 11:28:31 -0500 Subject: [PATCH 01/14] feat(nextjs) Add Getting Started guide --- .../guides/nextjs/getting-started.mdx | 520 ++++++++++++++++++ .../javascript/guides/nextjs/index.mdx | 347 ++++++++++-- .../javascript/guides/nextjs/manual-setup.mdx | 178 +----- .../javascript.nextjs.mdx | 52 ++ .../javascript.nextjs.mdx | 31 ++ .../pages-router-error/javascript.nextjs.mdx | 41 ++ 6 files changed, 961 insertions(+), 208 deletions(-) create mode 100644 docs/platforms/javascript/guides/nextjs/getting-started.mdx create mode 100644 platform-includes/getting-started-capture-errors/nextjs/app-router-global-error/javascript.nextjs.mdx create mode 100644 platform-includes/getting-started-capture-errors/nextjs/nested-server-components/javascript.nextjs.mdx create mode 100644 platform-includes/getting-started-capture-errors/nextjs/pages-router-error/javascript.nextjs.mdx diff --git a/docs/platforms/javascript/guides/nextjs/getting-started.mdx b/docs/platforms/javascript/guides/nextjs/getting-started.mdx new file mode 100644 index 0000000000000..a793e7662d7fd --- /dev/null +++ b/docs/platforms/javascript/guides/nextjs/getting-started.mdx @@ -0,0 +1,520 @@ +--- +title: "Getting Started with Sentry in Next.js" +description: Get started with monitoring errors, sending structure logs, replays, and sending traces and spans with span attributes within your in your Next.js application. +sidebar_title: "Getting Started" +sidebar_order: 0 +sdk: sentry.javascript.nextjs +categories: + - javascript + - browser + - server + - server-node +--- + + + +Unlike the [Quick Start](/platforms/javascript/guides/nextjs/) which focuses on the fastest path to getting started with Sentry, this guide focuses on getting you up and running with the core of Sentry's capabilities. + +## Install and Configure + + + + + + + +### Use the Installation Wizard (Recommended) + +The wizard will guide you through the setup process, asking you to enable additional (optional) Sentry features for your application beyond error monitoring. + +During this setup process, you'll be prompted to either create your project or select an existing project within Sentry. + + + + +```bash +npx @sentry/wizard@latest -i nextjs +``` + + + + + + + +## Modify your Sentry configuration + +If you want to expand on your Sentry configuration by adding additional functionality, or manually instrument your application, here are the configuration files the wizard would create. This configuration assumes you enabled all of the functionality the wizard proposed including replays, logs, and tracing. + + + + + + + +### Client-Side Configuration + +Configure Sentry for client-side error monitoring, performance tracking, and session replay. This configuration is loaded in the browser and includes integrations specific to the client environment. + +Key features: + +- **Browser Tracing**: Automatically tracks page loads and navigation +- **Session Replay**: Records user sessions for debugging +- **Structured Logs**: Sends client-side logs to Sentry +- **Performance Monitoring**: Captures 100% of transactions (adjust in production) + + + + +```javascript {tabTitle:Client} {filename:instrumentation-client.(js|ts)} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + sendDefaultPii: true, + + integrations: [ + // ___PRODUCT_OPTION_START___ performance + Sentry.browserTracingIntegration(), + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ session-replay + Sentry.replayIntegration(), + // ___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 + + tracesSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ session-replay + + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ session-replay +}); +``` + + + + + + + +### Server-Side Configuration + +Configure Sentry for server-side error monitoring and performance tracking. This runs in Node.js and handles API routes, server components, and server actions. + +Key features: + +- **Error Monitoring**: Captures server-side exceptions automatically +- **Structured Logs**: Sends server logs to Sentry +- **Performance Monitoring**: Tracks server-side operations and database queries + +For detailed manual setup instructions, see our [manual setup guide](/platforms/javascript/guides/nextjs/manual-setup/). + + + + +```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___ logs + + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs + + // ___PRODUCT_OPTION_START___ performance + tracesSampleRate: 1.0, + // ___PRODUCT_OPTION_END___ performance +}); +``` + + + + + + + +## Manually Capture Exceptions + +By default, Sentry captures errors and exceptions automatically within your application. For times where you want to manually capture, use `Sentry.captureException()`. This can be used anywhere you catch errors — on the server or in client components — to send exceptions to Sentry with full context. + + + + + + + +### App Router - Server Side + +Use `Sentry.captureException()` in API routes and Server Actions to manually capture exceptions that occur during server-side operations. + +**API Routes**: Capture errors in route handlers when fetching data or processing requests. + +**Server Actions**: Capture errors in form submissions or other server-side mutations. + +Both examples show how to capture the error while preserving your application's normal error handling flow. + + + + +```ts {filename:app/api/example/route.(ts|js)} +import * as Sentry from "@sentry/nextjs"; + +export async function GET() { + try { + // Your code that might throw + throw new Error("Failed to fetch data"); + } catch (err) { + Sentry.captureException(err); + // Optionally keep propagating the error + throw err; + } +} +``` + +```ts {filename:app/actions.ts} +"use server"; +import * as Sentry from "@sentry/nextjs"; + +export async function submitOrder(formData: FormData) { + try { + // ...perform work that might throw + } catch (err) { + Sentry.captureException(err); + throw err; // preserve default error behavior + } +} +``` + + + + + + + +### App Router - Client Side + +In client components, use `Sentry.captureException()` to capture errors from user interactions, async operations, or any client-side logic. + +The example shows capturing errors from a button click handler. You can optionally rethrow the error to trigger React error boundaries for user feedback. + + + + +```tsx {filename:app/example-client-component.(tsx|jsx)} +"use client"; +import * as Sentry from "@sentry/nextjs"; + +export function DangerousButton() { + const onClick = async () => { + try { + // Code that might throw (sync or async) + throw new Error("User action failed"); + } catch (err) { + Sentry.captureException(err); + // Optional: rethrow if you rely on React error boundaries + throw err; + } + }; + + return ; +} +``` + + + + + + + +### Pages Router - Server Side + +For applications using the Pages Router, capture exceptions in API routes to send server-side errors to Sentry. + +This example shows how to handle errors gracefully while still sending them to Sentry for monitoring. + + + + +```ts {filename:pages/api/example.(ts|js)} +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); + // Respond or rethrow based on your needs + res.status(500).json({ error: "Internal Server Error" }); + } +} +``` + + + + + + + +### Pages Router - Client Side + +In Pages Router client components, capture exceptions from event handlers and other client-side operations. + +Same pattern as App Router: catch the error, send it to Sentry, and optionally rethrow to trigger error boundaries. + + + + +```tsx {filename:components/DangerousButton.(tsx|jsx)} +import * as Sentry from "@sentry/nextjs"; + +export function DangerousButton() { + const onClick = () => { + try { + throw new Error("Something went wrong"); + } catch (err) { + Sentry.captureException(err); + // Optional: rethrow to trigger error boundaries + throw err; + } + }; + return ; +} +``` + + + + + + + +## Send Structured Logs + +{/* */} + + + + + + + +### Structured Logging + +[Structured logging](/platforms/javascript/guides/nextjs/logs/) lets users send text-based log information from their applications to Sentry. Once in Sentry, these logs can be viewed alongside relevant errors, searched by text-string, or searched using their individual attributes. + +Use Sentry's logger to capture structured logs with meaningful attributes that help you debug issues and understand user behavior. + + + + +```javascript +import * as Sentry from "@sentry/nextjs"; + +const { logger } = Sentry; + +logger.info("User completed checkout", { + userId: 123, + orderId: "order_456", + amount: 99.99, +}); + +logger.error("Payment processing failed", { + errorCode: "CARD_DECLINED", + userId: 123, + attemptCount: 3, +}); + +logger.warn(logger.fmt`Rate limit exceeded for user: ${123}`); +``` + + + + + + + +{/* */} + +## Customize Session Replay + +{/* */} + + + + + + + +### Session Replay Configuration + +[Replays](/product/explore/session-replay/web/getting-started/) allow you to see video-like reproductions of user sessions. + +By default, Session Replay masks sensitive data to protect privacy and PII data. If needed, you can modify the replay configurations in your client-side Sentry initialization to show (unmask) specific content that's safe to display. + + + Use caution when choosing what content to unmask within your application. This + content displays in Replays may contain sensitive information or PII data. + + + + + +```javascript {filename:instrumentation-client.(js|ts)} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [ + Sentry.replayIntegration({ + // This will show the content of the div with the class "reveal-content" and the span with the data-safe-to-show attribute + unmask: [".reveal-content", "[data-safe-to-show]"], + // This will show all text content in replays. Use with caution. + maskAllText: false, + // This will show all media content in replays. Use with caution. + blockAllMedia: false, + }), + ], + // Only capture replays for 10% of sessions + replaysSessionSampleRate: 0.1, + // Capture replays for 100% of sessions with an error + replaysOnErrorSampleRate: 1.0, +}); +``` + +```jsx +
This content will be visible in replays
+Safe user data +``` + +
+
+ +
+
+ +{/*
*/} + +## Add Custom Instrumentation + + + + + + + +### Custom Tracing + +[Tracing](/platforms/javascript/guides/nextjs/tracing/) allows you to monitor interactions between multiple services or applications. Create custom spans to measure specific operations and add meaningful attributes. This helps you understand performance bottlenecks and debug issues with detailed context. + + + + +```javascript +import * as Sentry from "@sentry/nextjs"; + +async function processUserData(userId) { + return await Sentry.startSpan( + { + name: "Process User Data", + op: "function", + attributes: { + userId: userId, + operation: "data_processing", + version: "2.1", + }, + }, + async () => { + const userData = await fetch(`/api/user?id=${userId}`).then((r) => + r.json() + ); + + return await Sentry.startSpan( + { + name: "Transform Data", + op: "transform", + attributes: { + recordCount: userData.length, + transformType: "normalize", + }, + }, + () => transformUserData(userData) + ); + } + ); +} + +const span = Sentry.getActiveSpan(); +if (span) { + span.setAttributes({ + cacheHit: true, + region: "us-west-2", + performanceScore: 0.95, + }); +} +``` + + + + + + + +## Test Your Setup + + + +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: + +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. + +### 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 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: + +- Learn more about [enriching the data (events) you send to Sentry](/platforms/javascript/guides/nextjs/enriching-events/) +- Explore more about configuring [Structured Logs](/platforms/javascript/guides/nextjs/logs/) in your application +- See advanced configurations for [Replays](/platforms/javascript/guides/nextjs/session-replay) +- Setup and configure [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/) between the different tiers and services for your application +- Learn how to use the [insights](/product/insights/) views in Sentry to explore performance related information about your application (including the Next.js specific view) + + + +- 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/) + + diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index ecbbfc013d52b..f04cef3df0351 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -13,19 +13,37 @@ categories: - +## Features + +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/). + +Send structured logs to Sentry and correlate them with errors and traces. + +Select which Sentry features you'd like to configure to get the corresponding setup instructions below. + + ## Install + + -To install Sentry using the installation wizard, run the command on the right within your project directory. +### Use the Installation Wizard (Recommended) -The wizard guides you through the setup process, asking you to enable additional (optional) Sentry features for your application beyond error monitoring. +The wizard will guide 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. +During this setup process, you'll be prompted to either create your project or select an existing project within Sentry. @@ -47,37 +65,30 @@ npx @sentry/wizard@latest -i nextjs + + ## Configure -If you prefer to configure Sentry manually, here are the configuration files the wizard would create: - -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/). - -Select which Sentry features you'd like to install in addition to Error Monitoring to get the corresponding installation and configuration instructions below. - - - - +If you want to expand on your Sentry configuration by adding additional functionality, or manually instrument your application, here are the configuration files the wizard would create. This configuration assumes you enabled all of the functionality the wizard proposed including replays, logs, and tracing. + + ### Client-Side Configuration -The wizard creates a client configuration file that initializes the Sentry SDK in your browser. +Configure Sentry for client-side error monitoring, performance tracking, and session replay. This configuration is loaded in the browser and includes integrations specific to the client environment. + +Key features: -The configuration includes your DSN (Data Source Name), which connects your app to your Sentry project, and enables the features you selected during installation. +- **Browser Tracing**: Automatically tracks page loads and navigation +- **Session Replay**: Records user sessions for debugging +- **Structured Logs**: Sends client-side logs to Sentry +- **Performance Monitoring**: Captures 100% of transactions (adjust in production) @@ -93,6 +104,9 @@ Sentry.init({ sendDefaultPii: true, integrations: [ + // ___PRODUCT_OPTION_START___ performance + Sentry.browserTracingIntegration(), + // ___PRODUCT_OPTION_END___ performance // ___PRODUCT_OPTION_START___ session-replay // Replay may only be enabled for the client-side Sentry.replayIntegration(), @@ -127,10 +141,6 @@ Sentry.init({ replaysOnErrorSampleRate: 1.0, // ___PRODUCT_OPTION_END___ session-replay }); - -// ___PRODUCT_OPTION_START___ performance -export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; -// ___PRODUCT_OPTION_END___ performance ``` @@ -141,9 +151,15 @@ export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; ### Server-Side Configuration -The wizard also creates a server configuration file for Node.js and Edge runtimes. +Configure Sentry for server-side error monitoring and performance tracking. This runs in Node.js and handles API routes, server components, and server actions. + +Key features: + +- **Error Monitoring**: Captures server-side exceptions automatically +- **Structured Logs**: Sends server logs to Sentry +- **Performance Monitoring**: Tracks server-side operations and database queries -For more advanced configuration options or to set up Sentry manually, check out our [manual setup guide](/platforms/javascript/guides/nextjs/manual-setup/). +For detailed manual setup instructions, see our [manual setup guide](/platforms/javascript/guides/nextjs/manual-setup/). @@ -159,16 +175,10 @@ Sentry.init({ sendDefaultPii: true, // ___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 }); @@ -176,7 +186,255 @@ Sentry.init({ + + + + +## Capture Next.js Errors + +Make sure runtime errors are surfaced to Sentry using Next.js error components. + + + + + + + +### App Router + +Create or update the `global-error.(tsx|jsx)` file to define a custom Next.js GlobalError component. This component will automatically capture errors that occur anywhere in your App Router application and send them to Sentry. + +The `useEffect` hook ensures the error is captured when the component mounts, while the `NextError` component provides a user-friendly error UI. + + + The installation wizard will scaffold this file for you if it's missing. + + + + + + + + + + + + + +#### Errors from Nested React Server Components + +To capture errors from nested React Server Components, use the `onRequestError` hook in your `instrumentation.(js|ts)` file. This ensures that errors occurring deep within your server component tree are properly captured by Sentry. + + + Requires `@sentry/nextjs` version `8.28.0` or higher and Next.js 15. + + + + + + + + + + + + + +### Pages Router + +For applications using the Pages Router, create or update the `_error.(tsx|jsx)` file to define a custom Next.js error page. This captures 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. + + + The installation wizard will scaffold this file for you if it's missing. + + + + + + + + + + + + + + + + + +## Send Structured Logs + + + + + + + +### Structured Logging + +[Structured logging](/platforms/javascript/guides/nextjs/logs/) lets users send text-based log information from their applications to Sentry. Once in Sentry, these logs can be viewed alongside relevant errors, searched by text-string, or searched using their individual attributes. + +Use Sentry's logger to capture structured logs with meaningful attributes that help you debug issues and understand user behavior. + + + + +```javascript +import * as Sentry from "@sentry/nextjs"; + +const { logger } = Sentry; + +logger.info("User completed checkout", { + userId: 123, + orderId: "order_456", + amount: 99.99, +}); + +logger.error("Payment processing failed", { + errorCode: "CARD_DECLINED", + userId: 123, + attemptCount: 3, +}); + +logger.warn(logger.fmt`Rate limit exceeded for user: ${123}`); +``` + + + + + + + + + + + +## Customize Session Replay + + + + + + + +### Session Replay Configuration + +[Replays](/product/explore/session-replay/web/getting-started/) allow you to see video-like reproductions of user sessions. + +By default, Session Replay masks sensitive data to protect privacy and PII data. If needed, you can modify the replay configurations in your client-side Sentry initialization to show (unmask) specific content that's safe to display. + + + Use caution when choosing what content to unmask within your application. This + content displays in Replays may contain sensitive information or PII data. + + + + + +```javascript {filename:instrumentation-client.(js|ts)} +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [ + Sentry.replayIntegration({ + // This will show the content of the div with the class "reveal-content" and the span with the data-safe-to-show attribute + unmask: [".reveal-content", "[data-safe-to-show]"], + // This will show all text content in replays. Use with caution. + maskAllText: false, + // This will show all media content in replays. Use with caution. + blockAllMedia: false, + }), + ], + // Only capture replays for 10% of sessions + replaysSessionSampleRate: 0.1, + // Capture replays for 100% of sessions with an error + replaysOnErrorSampleRate: 1.0, +}); +``` + +```jsx +
This content will be visible in replays
+Safe user data +``` + +
+
+
+
+ +
+ +## Add Custom Instrumentation + + + + + + + +### Custom Tracing + +[Tracing](/platforms/javascript/guides/nextjs/tracing/) allows you to monitor interactions between multiple services or applications. Create custom spans to measure specific operations and add meaningful attributes. This helps you understand performance bottlenecks and debug issues with detailed context. + + + + +```javascript +import * as Sentry from "@sentry/nextjs"; + +async function processUserData(userId) { + return await Sentry.startSpan( + { + name: "Process User Data", + op: "function", + attributes: { + userId: userId, + operation: "data_processing", + version: "2.1", + }, + }, + async () => { + const userData = await fetch(`/api/user?id=${userId}`).then((r) => + r.json() + ); + + return await Sentry.startSpan( + { + name: "Transform Data", + op: "transform", + attributes: { + recordCount: userData.length, + transformType: "normalize", + }, + }, + () => transformUserData(userData) + ); + } + ); +} + +const span = Sentry.getActiveSpan(); +if (span) { + span.setAttributes({ + cacheHit: true, + region: "us-west-2", + performanceScore: 0.95, + }); +} +``` + + + + + + + +
## Verify Your Setup @@ -192,12 +450,6 @@ If you haven't tested your Sentry configuration yet, let's do it now. You can co 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. - - - ### 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). @@ -213,12 +465,11 @@ At this point, you should have integrated Sentry into your Next.js application a 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) -- 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/) +- Learn more about [enriching the data (events) you send to Sentry](/platforms/javascript/guides/nextjs/enriching-events/) +- Explore more about configuring [Structured Logs](/platforms/javascript/guides/nextjs/logs/) in your application +- See advanced configurations for [Replays](/platforms/javascript/guides/nextjs/session-replay) +- Setup and configure [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/) between the different tiers and services for your application +- Learn how to use the [insights](/product/insights/) views in Sentry to explore performance related information about your application (including the Next.js specific view) @@ -226,5 +477,3 @@ Our next recommended steps for you are: - [Get support](https://sentry.zendesk.com/hc/en-us/) - -
diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup.mdx index 3e949a48199d1..e874e34619fad 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup.mdx @@ -13,6 +13,20 @@ description: "Learn how to manually set up Sentry in your Next.js app and captur ## Step 1: Install +Choose the features you want to configure, and this guide will show you how: + + + + + ### Install the Sentry SDK Run the command for your preferred package manager to add the Sentry SDK to your application: @@ -31,20 +45,6 @@ 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: @@ -263,154 +263,15 @@ To capture React render errors, you need to add error components for the App Rou ### 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) @@ -537,8 +398,6 @@ module.exports = withSentryConfig(nextConfig, { ## Step 8: Verify - - Let's test your setup and confirm that Sentry is working correctly and sending data to your Sentry project. ### Issues @@ -615,6 +474,8 @@ Now, head over to your project on [Sentry.io](https://sentry.io) to view the col + + ## 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. @@ -627,7 +488,6 @@ Our next recommended steps for you are: - 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/) 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; +``` From aa07db081443f3b89692cf7adc146264ed116400 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Tue, 9 Dec 2025 17:21:32 -0500 Subject: [PATCH 02/14] manual setup is now stepped through --- .../javascript/guides/nextjs/manual-setup.mdx | 283 ++++++++++++++---- 1 file changed, 230 insertions(+), 53 deletions(-) diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup.mdx index e874e34619fad..4647fa7494121 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup.mdx @@ -11,6 +11,8 @@ description: "Learn how to manually set up Sentry in your Next.js app and captur + + ## Step 1: Install Choose the features you want to configure, and this guide will show you how: @@ -27,9 +29,17 @@ Choose the features you want to configure, and this guide will show you how: + + + + + ### Install the Sentry SDK -Run the command for your preferred package manager to add the Sentry SDK to your application: +Run the command for your preferred package manager to add the Sentry SDK to your application. + + + ```bash {tabTitle:npm} npm install @sentry/nextjs --save @@ -43,11 +53,26 @@ yarn add @sentry/nextjs pnpm add @sentry/nextjs ``` + + + + + ## Step 2: Configure + + + + + ### Apply Instrumentation to Your App -Extend your app's default Next.js options by adding `withSentryConfig` into your `next.config.(js|mjs)` file: +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"); @@ -91,7 +116,11 @@ export default withSentryConfig(nextConfig, { }); ``` - + + + + + ### Initialize Sentry Client-Side and Server-Side SDKs @@ -102,13 +131,21 @@ Create three files in your application's root directory: - `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: +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. + + Include your DSN directly in these files, or use a _public_ environment + variable like `NEXT_PUBLIC_SENTRY_DSN`. + + + + + ```javascript {tabTitle:Client} {filename:instrumentation-client.(js|ts)} import * as Sentry from "@sentry/nextjs"; @@ -227,14 +264,27 @@ Sentry.init({ }); ``` - - 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: +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. + + + 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`. + + + + ```javascript {filename:instrumentation.(js|ts)} export async function register() { @@ -248,36 +298,93 @@ export async function register() { } ``` - - 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. This component will automatically capture errors that occur anywhere in your App Router application and send them to Sentry. + +The `useEffect` hook ensures the error is captured when the component mounts, while the `NextError` component provides a user-friendly error UI. + + + + + + + + + + #### Errors from Nested React Server Components +To capture errors from nested React Server Components, use the `onRequestError` hook in your `instrumentation.(js|ts)` file. This ensures that errors occurring deep within your server component tree are properly captured by Sentry. + + + Requires `@sentry/nextjs` version `8.28.0` or higher and Next.js 15. + + + + + + + + + + + ### Pages Router +For applications using the Pages Router, create or update the `_error.(tsx|jsx)` file to define a custom Next.js error page. This captures 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. + + + + + + + + + ## 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: + + + + + +### Configure Source Maps + +Update your `next.config.(js|mjs)` file with the auth token option and set the `SENTRY_AUTH_TOKEN` environment variable in your `.env` file. + + + +Make sure to keep your auth token secret and out of version control. + + + + + ```javascript {tabTitle:ESM} {filename:next.config.mjs} export default withSentryConfig(nextConfig, { @@ -303,27 +410,43 @@ module.exports = withSentryConfig(nextConfig, { }); ``` -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. + + + + + +### Configure Tunnel Route + 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 + + 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. + + + + + ```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. @@ -342,20 +465,32 @@ module.exports = withSentryConfig(nextConfig, { }); ``` - - 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: + + + + + +### Configure Cron Monitoring + +Update `withSentryConfig` in your `next.config.(js|mjs)` file with the `automaticVercelMonitors` option. + + + +Automatic Vercel cron jobs instrumentation currently only supports the Pages Router. App Router route handlers are not yet supported. + + + + + ```javascript {tabTitle:ESM} {filename:next.config.mjs} export default withSentryConfig(nextConfig, { @@ -369,16 +504,26 @@ module.exports = withSentryConfig(nextConfig, { }); ``` - + + -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: + + + + + + +### Configure Component Annotation + +Update `withSentryConfig` in your `next.config.(js|mjs)` file with the `reactComponentAnnotation` option. + + + ```javascript {tabTitle:ESM} {filename:next.config.mjs} export default withSentryConfig(nextConfig, { @@ -396,13 +541,33 @@ module.exports = withSentryConfig(nextConfig, { }); ``` + + + + + ## 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: +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. + + + 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. + + + + + + ```jsx ; -} -``` - - - - - - - -### Pages Router - Server Side - -For applications using the Pages Router, capture exceptions in API routes to send server-side errors to Sentry. - -This example shows how to handle errors gracefully while still sending them to Sentry for monitoring. - - - - -```ts {filename:pages/api/example.(ts|js)} -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); - // Respond or rethrow based on your needs - res.status(500).json({ error: "Internal Server Error" }); - } -} -``` - - - - - - - -### Pages Router - Client Side - -In Pages Router client components, capture exceptions from event handlers and other client-side operations. - -Same pattern as App Router: catch the error, send it to Sentry, and optionally rethrow to trigger error boundaries. - - - - -```tsx {filename:components/DangerousButton.(tsx|jsx)} -import * as Sentry from "@sentry/nextjs"; - -export function DangerousButton() { - const onClick = () => { - try { - throw new Error("Something went wrong"); - } catch (err) { - Sentry.captureException(err); - // Optional: rethrow to trigger error boundaries - throw err; - } - }; - return ; -} -``` - - - - - - - -## Send Structured Logs - -{/* */} - - - - - - - -### Structured Logging - -[Structured logging](/platforms/javascript/guides/nextjs/logs/) lets users send text-based log information from their applications to Sentry. Once in Sentry, these logs can be viewed alongside relevant errors, searched by text-string, or searched using their individual attributes. - -Use Sentry's logger to capture structured logs with meaningful attributes that help you debug issues and understand user behavior. - - - - -```javascript -import * as Sentry from "@sentry/nextjs"; - -const { logger } = Sentry; - -logger.info("User completed checkout", { - userId: 123, - orderId: "order_456", - amount: 99.99, -}); - -logger.error("Payment processing failed", { - errorCode: "CARD_DECLINED", - userId: 123, - attemptCount: 3, -}); - -logger.warn(logger.fmt`Rate limit exceeded for user: ${123}`); -``` - - - - - - - -{/* */} - -## Customize Session Replay - -{/* */} - - - - - - - -### Session Replay Configuration - -[Replays](/product/explore/session-replay/web/getting-started/) allow you to see video-like reproductions of user sessions. - -By default, Session Replay masks sensitive data to protect privacy and PII data. If needed, you can modify the replay configurations in your client-side Sentry initialization to show (unmask) specific content that's safe to display. - - - Use caution when choosing what content to unmask within your application. This - content displays in Replays may contain sensitive information or PII data. - - - - - -```javascript {filename:instrumentation-client.(js|ts)} -import * as Sentry from "@sentry/nextjs"; - -Sentry.init({ - dsn: "___PUBLIC_DSN___", - integrations: [ - Sentry.replayIntegration({ - // This will show the content of the div with the class "reveal-content" and the span with the data-safe-to-show attribute - unmask: [".reveal-content", "[data-safe-to-show]"], - // This will show all text content in replays. Use with caution. - maskAllText: false, - // This will show all media content in replays. Use with caution. - blockAllMedia: false, - }), - ], - // Only capture replays for 10% of sessions - replaysSessionSampleRate: 0.1, - // Capture replays for 100% of sessions with an error - replaysOnErrorSampleRate: 1.0, -}); -``` - -```jsx -
This content will be visible in replays
-Safe user data -``` - -
-
- -
-
- -{/*
*/} - -## Add Custom Instrumentation - - - - - - - -### Custom Tracing - -[Tracing](/platforms/javascript/guides/nextjs/tracing/) allows you to monitor interactions between multiple services or applications. Create custom spans to measure specific operations and add meaningful attributes. This helps you understand performance bottlenecks and debug issues with detailed context. - - - - -```javascript -import * as Sentry from "@sentry/nextjs"; - -async function processUserData(userId) { - return await Sentry.startSpan( - { - name: "Process User Data", - op: "function", - attributes: { - userId: userId, - operation: "data_processing", - version: "2.1", - }, - }, - async () => { - const userData = await fetch(`/api/user?id=${userId}`).then((r) => - r.json() - ); - - return await Sentry.startSpan( - { - name: "Transform Data", - op: "transform", - attributes: { - recordCount: userData.length, - transformType: "normalize", - }, - }, - () => transformUserData(userData) - ); - } - ); -} - -const span = Sentry.getActiveSpan(); -if (span) { - span.setAttributes({ - cacheHit: true, - region: "us-west-2", - performanceScore: 0.95, - }); -} -``` - - - - - - - -## Test Your Setup - - - -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: - -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. - -### 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 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: - -- Learn more about [enriching the data (events) you send to Sentry](/platforms/javascript/guides/nextjs/enriching-events/) -- Explore more about configuring [Structured Logs](/platforms/javascript/guides/nextjs/logs/) in your application -- See advanced configurations for [Replays](/platforms/javascript/guides/nextjs/session-replay) -- Setup and configure [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/) between the different tiers and services for your application -- Learn how to use the [insights](/product/insights/) views in Sentry to explore performance related information about your application (including the Next.js specific view) - - - -- 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/) - - diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index ecbbfc013d52b..e81313e12c00d 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -17,208 +17,114 @@ categories: ## Install - - - - -To install Sentry using the installation wizard, run the command on the right within your project directory. - -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. - - - -- 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 - - - - - +Run the Sentry wizard to automatically configure Sentry in your Next.js application: ```bash npx @sentry/wizard@latest -i nextjs ``` - - - - -## Configure - -If you prefer to configure Sentry manually, here are the configuration files the wizard would create: - -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/). - -Select which Sentry features you'd like to install in addition to Error Monitoring to get the corresponding installation and configuration instructions below. - - - - - - - - - -### 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. - - - - -```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, - - 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___ 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)} -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___ 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 -}); -``` - - - - - -## Verify Your Setup - - - -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: - -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 +We recommend enabling all features during setup: [Tracing](/platforms/javascript/guides/nextjs/tracing/), [Session Replay](/platforms/javascript/guides/nextjs/session-replay/), and [Logs](/platforms/javascript/guides/nextjs/logs/). You can always adjust them later. -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. +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/)) -### View Captured Data in Sentry +It automatically creates and configures the following files: -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). +- `sentry.server.config.ts` - Server-side SDK initialization +- `sentry.edge.config.ts` - Edge runtime SDK initialization +- `instrumentation-client.ts` - Client-side 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`) - +
- +## Verify + +After the wizard completes, verify that Sentry is working correctly by going through this checklist: + + Replays", + link: "/platforms/javascript/guides/nextjs/session-replay/", + linkText: "Learn about Replay", + }, + { + id: "see-logs", + label: "See Logs", + description: + "View structured logs from your application. Go to Explore -> Logs", + link: "/platforms/javascript/guides/nextjs/logs/", + linkText: "Learn about Logs", + }, + { + id: "see-traces", + label: "See Traces", + description: + "View the performance trace from the button click. Go to Explore -> Traces", + link: "/platforms/javascript/guides/nextjs/tracing/", + linkText: "Learn about Tracing", + }, + { + id: "build-and-start", + label: "Build and start production server", + description: "Run: npm run build && npm run start", + }, + { + id: "verify-sourcemaps", + label: "Verify Source Maps", + description: + "Trigger another error on /sentry-example-page, then check the new error in Sentry — the stack trace should show your original source code, not minified code", + link: "/platforms/javascript/guides/nextjs/sourcemaps/", + linkText: "Learn about Source Maps", + }, + ]} +/> ## 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/) +- [Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) - Configure Sentry manually or customize your setup +- [Capturing Errors](/platforms/javascript/guides/nextjs/usage/) - Learn how to manually capture errors +- [Session Replay](/platforms/javascript/guides/nextjs/session-replay/) - Configure video-like reproductions of user sessions +- [Tracing](/platforms/javascript/guides/nextjs/tracing/) - Set up distributed tracing across your application +- [Logs](/platforms/javascript/guides/nextjs/logs/) - Send structured logs to Sentry +- [Configuration Options](/platforms/javascript/guides/nextjs/configuration/) - Customize your Sentry configuration 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 4647fa7494121..0000000000000 --- a/docs/platforms/javascript/guides/nextjs/manual-setup.mdx +++ /dev/null @@ -1,674 +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 - -Choose the features you want to configure, and this guide will show you how: - - - - - - - - - - -### 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 - - - - - - -### 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. - - - - Include your DSN directly in these files, or use a _public_ environment - variable like `NEXT_PUBLIC_SENTRY_DSN`. - - - - - -```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 -}); -``` - - - - - - - -### 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. - - - 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`. - - - - - -```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"); - } -} -``` - - - - - - -## 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. This component will automatically capture errors that occur anywhere in your App Router application and send them to Sentry. - -The `useEffect` hook ensures the error is captured when the component mounts, while the `NextError` component provides a user-friendly error UI. - - - - - - - - - - - - -#### Errors from Nested React Server Components - -To capture errors from nested React Server Components, use the `onRequestError` hook in your `instrumentation.(js|ts)` file. This ensures that errors occurring deep within your server component tree are properly captured by Sentry. - - - Requires `@sentry/nextjs` version `8.28.0` or higher and Next.js 15. - - - - - - - - - - - - - -### Pages Router - -For applications using the Pages Router, create or update the `_error.(tsx|jsx)` file to define a custom Next.js error page. This captures 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. - - - - - - - - - - - -## 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. - - - - - - -### Configure Source Maps - -Update your `next.config.(js|mjs)` file with the auth token option and set the `SENTRY_AUTH_TOKEN` environment variable in your `.env` file. - - - -Make sure to keep your auth token secret and out of version control. - - - - - - -```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, -}); -``` - -```sh {filename:.env} -SENTRY_AUTH_TOKEN=___ORG_AUTH_TOKEN___ -``` - - - - - - -## 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. - - - - - - -### Configure Tunnel Route - -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 - - - 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. - - - - - -```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) -}); -``` - - - - - - -## 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). - - - - - - -### Configure Cron Monitoring - -Update `withSentryConfig` in your `next.config.(js|mjs)` file with the `automaticVercelMonitors` option. - - - -Automatic Vercel cron jobs instrumentation currently only supports the Pages Router. App Router route handlers are not yet supported. - - - - - - -```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, -}); -``` - - - - - - -## 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. - - - - - - -### Configure Component Annotation - -Update `withSentryConfig` in your `next.config.(js|mjs)` file with the `reactComponentAnnotation` 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. - - - 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. - - - - - - - -```jsx - -``` - - - - - - - - - - - - - -### Tracing - -To test tracing, create a test API route like `/api/sentry-example-api` and update your test button to call this route and throw an error if the response isn't `ok`. - -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. - - - - -```javascript {filename:app/api/sentry-example-api/route.js} -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..." }); -} -``` - -```jsx -; -``` - - - - - - - - -### 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/) - - - -- 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..74fa5468e15c5 --- /dev/null +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx @@ -0,0 +1,515 @@ +--- +title: "Manual Setup" +sidebar_order: 1 +description: "Learn how to manually set up Sentry in your Next.js app with Turbopack and App Router." +--- + + + 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 + + + + + +## Install + +Choose the features you want to configure, and this guide will show you how: + + + + + + + + + + +### 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 + + + + + + +### Global Error Component + +Create `app/global-error.tsx` to capture errors that occur anywhere in your App Router application. + + + + + + + + + + + + +### Nested React 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. + + + + + + + + + + + + +## Manually Capture Exceptions + +By default, Sentry captures errors automatically. For cases where you want to manually capture errors, use `Sentry.captureException()`. + + + + + + +### Server-Side (API Routes & Server Actions) + +Capture errors in API route handlers and Server Actions. + + + + +```typescript {filename:app/api/example/route.ts} +import * as Sentry from "@sentry/nextjs"; + +export async function GET() { + try { + // Your code that might throw + throw new Error("Failed to fetch data"); + } catch (err) { + Sentry.captureException(err); + throw err; // Optionally re-throw + } +} +``` + +```typescript {filename:app/actions.ts} +"use server"; +import * as Sentry from "@sentry/nextjs"; + +export async function submitOrder(formData: FormData) { + try { + // ...perform work that might throw + } catch (err) { + Sentry.captureException(err); + throw err; + } +} +``` + + + + + + + +### Client-Side + +Capture errors from user interactions and client-side logic. + + + + +```tsx {filename:app/components/DangerousButton.tsx} +"use client"; +import * as Sentry from "@sentry/nextjs"; + +export function DangerousButton() { + const onClick = async () => { + try { + throw new Error("User action failed"); + } catch (err) { + Sentry.captureException(err); + throw err; // Optional: trigger error boundaries + } + }; + + return ; +} +``` + + + + + + +## Source Maps (Optional) + +Enable readable stack traces by uploading source maps to Sentry. + + + + + + +### Configure Source Maps + +Add the `authToken` option to your `next.config.ts` and set the `SENTRY_AUTH_TOKEN` environment variable. + + + 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. + + + + + + +### Configure Tunnel Route + +Set `tunnelRoute: true` to automatically generate a random tunnel route for each build. + + + This increases server load. Consider the trade-off for your application. + + + + + +```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", +}); +``` + + + + + + +## React Component Names (Optional) + +Capture React component names to see which component a user clicked on in Session Replay. + + + + + + +### Enable Component Annotation + + + + +```typescript {filename:next.config.ts} +export default withSentryConfig(nextConfig, { + reactComponentAnnotation: { + enabled: true, + }, +}); +``` + + + + + + +## Verify + +Test your setup by creating a button that throws an error: + + + + + + +Add this button to any page and click it to trigger a test error. + + + + + + +```jsx + +``` + + + + + + +Head over to your project on [Sentry.io](https://sentry.io) to view the captured error. + + + + + +## Next Steps + +- [Structured Logs](/platforms/javascript/guides/nextjs/logs/) - Send logs to Sentry +- [Session Replay](/platforms/javascript/guides/nextjs/session-replay/) - Configure replay options +- [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/) - Set up tracing across services +- [Enriching Events](/platforms/javascript/guides/nextjs/enriching-events/) - Add context to your events +- [Configuration Options](/platforms/javascript/guides/nextjs/configuration/) - All 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..9a64a206c8d7b --- /dev/null +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx @@ -0,0 +1,155 @@ +--- +title: "Pages Router Setup" +sidebar_order: 2 +description: "Additional setup steps for Next.js applications using the Pages Router." +--- + +This guide covers the additional configuration needed for **Next.js applications using the Pages Router**. Complete the [main manual setup](/platforms/javascript/guides/nextjs/manual-setup/) first, then follow these steps. + + + If you're using the App Router exclusively, you don't need this guide. See the + [main manual setup](/platforms/javascript/guides/nextjs/manual-setup/) + instead. + + +## Capture Pages Router Errors + + + + + + +### Custom Error Page + +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. + + + + + + + + + + + +## Manually Capture Exceptions + + + + + + +### Pages Router API Routes + +Capture errors in Pages Router API routes using `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" }); + } +} +``` + + + + + + + +### Client-Side Components + +Capture exceptions from event handlers and other client-side operations. + + + + +```tsx {filename:components/DangerousButton.tsx} +import * as Sentry from "@sentry/nextjs"; + +export function DangerousButton() { + const onClick = () => { + try { + throw new Error("Something went wrong"); + } catch (err) { + Sentry.captureException(err); + throw err; // Optional: trigger error boundaries + } + }; + return ; +} +``` + + + + + + +## 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 [main manual 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 will share the same Sentry configuration files + + + The Sentry SDK automatically detects which router is being used and applies + the appropriate instrumentation. + + +## Next Steps + +- [Main Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) - Complete setup guide +- [Webpack Setup](/platforms/javascript/guides/nextjs/manual-setup/webpack-setup/) - For applications not using Turbopack +- [Troubleshooting](/platforms/javascript/guides/nextjs/troubleshooting/) - Common issues and solutions 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..241578a759426 --- /dev/null +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx @@ -0,0 +1,244 @@ +--- +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 | During build via Webpack plugin | +| 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. + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + // Instrument API routes and data fetching methods (default: true) + autoInstrumentServerFunctions: true, + + // Instrument Next.js middleware (default: true) + autoInstrumentMiddleware: true, + + // Instrument app directory 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 + +Webpack uploads source maps during the build process using the Sentry Webpack Plugin, while Turbopack uploads them after the build completes. + + + + + + +### Webpack Plugin Options + +Pass options directly to the underlying Sentry Webpack Plugin for advanced configuration. + + + The `unstable_sentryWebpackPluginOptions` API may change in future releases. + + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + // Standard options + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + authToken: process.env.SENTRY_AUTH_TOKEN, + + // Advanced Webpack plugin options + unstable_sentryWebpackPluginOptions: { + // Custom source map settings + sourcemaps: { + assets: ["./build/**/*.js", "./build/**/*.map"], + ignore: ["node_modules/**"], + }, + }, +}); +``` + + + + + + +## Server Actions with Webpack + +When using Webpack, Server Actions are instrumented automatically through the `autoInstrumentServerFunctions` option. However, you may want to add manual error capturing for better control: + + + + + + +### Manual Server Action Instrumentation + +For more control over error handling in Server Actions, manually wrap them with `Sentry.captureException()`. + + + + +```typescript {filename:app/actions.ts} +"use server"; +import * as Sentry from "@sentry/nextjs"; + +export async function submitForm(formData: FormData) { + try { + // Your server action logic + const result = await processForm(formData); + return { success: true, data: result }; + } catch (err) { + // Capture with additional context + Sentry.captureException(err, { + extra: { + formFields: Object.fromEntries(formData), + }, + }); + return { success: false, error: "Submission failed" }; + } +} +``` + + + + + + +## Tunneling with Webpack + +Unlike Turbopack, Webpack automatically configures your middleware to skip the tunnel route. You don't need to add manual exclusions. + + + + + + +### Simple Tunnel Configuration + +With Webpack, just enable the tunnel route and Sentry handles the middleware configuration automatically. + + + + +```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-specific options** - `excludeServerRoutes` and `unstable_sentryWebpackPluginOptions` have no effect with Turbopack +2. **Update tunnel route configuration** - If using middleware, add a negative matcher to exclude your tunnel route +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) - excludeServerRoutes is not needed +export default withSentryConfig(nextConfig, { + tunnelRoute: "/monitoring", // Use fixed route with Turbopack +}); +``` + +## Next Steps + +- [Main Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) - Complete setup guide +- [Pages Router Setup](/platforms/javascript/guides/nextjs/manual-setup/pages-router/) - For Pages Router applications +- [Build Options](/platforms/javascript/guides/nextjs/configuration/build/) - All build configuration options +- [Troubleshooting](/platforms/javascript/guides/nextjs/troubleshooting/) - Common issues and solutions 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..0fddb5cc89ae0 --- /dev/null +++ b/src/components/verificationChecklist/index.tsx @@ -0,0 +1,198 @@ +'use client'; + +import {useCallback, useEffect, useState} from 'react'; +import {ArrowRightIcon, CheckIcon} from '@radix-ui/react-icons'; + +import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent'; + +import styles from './style.module.scss'; + +type ChecklistItem = { + id: string; + label: string; + description?: string; + link?: string; + linkText?: string; +}; + +type Props = { + /** Unique identifier for this checklist (used for localStorage) */ + checklistId?: string; + /** Items to display in the checklist */ + items?: ChecklistItem[]; +}; + +const DEFAULT_ITEMS: ChecklistItem[] = [ + { + id: 'trigger-error', + label: 'Trigger a test error', + description: 'Use the example code or page to generate an error', + }, + { + id: 'see-error', + label: 'See the error in Sentry', + description: "Check your project's Issues page", + }, + { + id: 'run-build', + label: 'Run a production build', + description: 'Verify source maps are uploaded', + }, +]; + +function getStorageKey(checklistId: string): string { + return `sentry-docs-checklist-${checklistId}`; +} + +export function VerificationChecklist({ + checklistId = 'default', + items = DEFAULT_ITEMS, +}: Props) { + const [checkedItems, setCheckedItems] = useState>({}); + const [mounted, setMounted] = useState(false); + const {emit} = usePlausibleEvent(); + + // Load checked items from localStorage on mount + useEffect(() => { + setMounted(true); + try { + const stored = localStorage.getItem(getStorageKey(checklistId)); + if (stored) { + setCheckedItems(JSON.parse(stored)); + } + } catch { + // Ignore localStorage errors + } + }, [checklistId]); + + // 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]); + + const completedCount = Object.values(checkedItems).filter(Boolean).length; + const totalCount = items.length; + const allComplete = completedCount === totalCount; + + 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: { + page: window.location.pathname, + checklistId, + itemId, + itemLabel, + checked: newChecked, + }, + }); + + // Check if all items are now complete + const newCompletedCount = Object.values(newState).filter(Boolean).length; + if (newCompletedCount === totalCount && newChecked) { + emit('Checklist Complete', { + props: { + page: window.location.pathname, + checklistId, + }, + }); + } + + return newState; + }); + }, + [checklistId, emit, totalCount] + ); + + const handleLinkClick = useCallback( + (itemId: string, linkText: string, link: string) => { + emit('Checklist Link Click', { + props: { + page: window.location.pathname, + checklistId, + itemId, + linkText, + link, + }, + }); + }, + [checklistId, emit] + ); + + return ( +
+
+
+
+
+ + {completedCount} of {totalCount} complete + +
+ + + + {allComplete && ( +
+ + All done! Sentry is successfully configured. +
+ )} +
+ ); +} diff --git a/src/components/verificationChecklist/style.module.scss b/src/components/verificationChecklist/style.module.scss new file mode 100644 index 0000000000000..3c74f83b8d82f --- /dev/null +++ b/src/components/verificationChecklist/style.module.scss @@ -0,0 +1,242 @@ +.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 { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem; + border-radius: 0.375rem; + background: var(--white); + border: 1px solid var(--gray-100); + transition: border-color 0.15s ease, background-color 0.15s ease; + + &:hover { + border-color: var(--gray-200); + } + + // Mobile: stack content and link vertically + @media (max-width: 640px) { + flex-direction: column; + gap: 0.5rem; + } +} + +.label { + display: flex; + align-items: flex-start; + gap: 0.75rem; + cursor: pointer; + flex: 1; + min-width: 0; // Allow text to wrap + + &.checked { + opacity: 0.7; + } +} + +.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; + + &.checked { + background: #6c5fc7; + border-color: #6c5fc7; + } +} + +.checkIcon { + width: 14px; + height: 14px; + color: white; +} + +.content { + display: flex; + flex-direction: column; + gap: 0.125rem; + min-width: 0; // Allow text to wrap +} + +.labelText { + font-weight: 500; + color: var(--gray-900); + transition: color 0.15s ease; + + &.checked { + text-decoration: line-through; + color: var(--gray-500); + } +} + +.description { + font-size: 0.875rem; + color: var(--gray-500); +} + +.link { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: 0.875rem; + color: #6c5fc7; + text-decoration: none; + white-space: nowrap; + flex-shrink: 0; + + &:hover { + text-decoration: underline; + } + + // Mobile: align link to the left with some indent + @media (max-width: 640px) { + margin-left: 2rem; // Align with text content (checkbox width + gap) + } +} + +.arrowIcon { + width: 14px; + height: 14px; +} + +.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; +} + +// Dark mode support +:global(.dark) { + .checklist { + background: #1a1523; + border-color: #3e3446; + } + + .item { + background: #231c3d; + border-color: #3e3446; + + &:hover { + border-color: #584774; + } + } + + .customCheckbox { + background: #231c3d; + border-color: #584774; + + &.checked { + background: #8b7fd9; + border-color: #8b7fd9; + } + } + + .labelText { + color: #e2d9e9; + + &.checked { + color: #9481a4; + } + } + + .description { + color: #a796b4; + } + + .progressBar { + background: #3e3446; + } + + .progressText { + color: #bbadc6; + } + + .link { + color: #a796f0; + + &:hover { + color: #c4baff; + } + } + + .successMessage { + background: rgba(45, 106, 79, 0.3); + border-color: #40916c; + color: #69db7c; + } +} 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..c20b02d774b2e 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 {VerificationChecklist} from './components/verificationChecklist'; import {VersionRequirement} from './components/version-requirement'; import {VimeoEmbed} from './components/video'; @@ -116,6 +117,7 @@ export function mdxComponents( SplitSectionCode, StepComponent, StepConnector, + VerificationChecklist, VimeoEmbed, VersionRequirement, a: SmartLink, From 905399866dd975d6b18398f538e607333d7bb3c7 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 00:23:53 +0000 Subject: [PATCH 05/14] [getsentry/action-github-commit] Auto commit --- src/components/verificationChecklist/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 0fddb5cc89ae0..7f28c6f36daea 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -157,12 +157,16 @@ export function VerificationChecklist({ onChange={() => toggleItem(item.id, item.label)} className={styles.checkbox} /> - + {isChecked && } - + {item.label} {item.description && ( From 678b5d9a29342654f9dc564fa9c4fcf1ee87b104 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Wed, 10 Dec 2025 20:48:32 -0500 Subject: [PATCH 06/14] conditional checklist --- .../javascript/guides/nextjs/index.mdx | 75 +++++----- .../verificationChecklist/index.tsx | 134 ++++++++++++++---- .../verificationChecklist/style.module.scss | 30 +++- 3 files changed, 171 insertions(+), 68 deletions(-) diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index e81313e12c00d..6226332912124 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 @@ -23,12 +23,6 @@ Run the Sentry wizard to automatically configure Sentry in your Next.js applicat npx @sentry/wizard@latest -i nextjs ``` - - -We recommend enabling all features during setup: [Tracing](/platforms/javascript/guides/nextjs/tracing/), [Session Replay](/platforms/javascript/guides/nextjs/session-replay/), and [Logs](/platforms/javascript/guides/nextjs/logs/). You can always adjust them later. - - - The wizard will guide you through: @@ -53,7 +47,11 @@ It automatically creates and configures the following files: ## Verify -After the wizard completes, verify that Sentry is working correctly by going through this checklist: +Select the features you enabled in the wizard, then go through the checklist to verify your setup: + + Replays", - link: "/platforms/javascript/guides/nextjs/session-replay/", - linkText: "Learn about Replay", + description: "Watch a video-like reproduction of the error", + link: "https://sentry.io/orgredirect/organizations/:orgslug/replays/", + linkText: "Open Replays", + docsLink: "/platforms/javascript/guides/nextjs/session-replay/", + docsLinkText: "Learn more", + optionId: "session-replay", }, { id: "see-logs", label: "See Logs", - description: - "View structured logs from your application. Go to Explore -> Logs", - link: "/platforms/javascript/guides/nextjs/logs/", - linkText: "Learn about Logs", + description: "View structured logs from your application", + link: "https://sentry.io/orgredirect/organizations/:orgslug/explore/logs/", + linkText: "Open Logs", + docsLink: "/platforms/javascript/guides/nextjs/logs/", + docsLinkText: "Learn more", + optionId: "logs", }, { id: "see-traces", label: "See Traces", - description: - "View the performance trace from the button click. Go to Explore -> Traces", - link: "/platforms/javascript/guides/nextjs/tracing/", - linkText: "Learn about Tracing", - }, - { - id: "build-and-start", - label: "Build and start production server", - description: "Run: npm run build && npm run start", - }, - { - id: "verify-sourcemaps", - label: "Verify Source Maps", - description: - "Trigger another error on /sentry-example-page, then check the new error in Sentry — the stack trace should show your original source code, not minified code", - link: "/platforms/javascript/guides/nextjs/sourcemaps/", - linkText: "Learn about Source Maps", + description: "View the performance trace from the button click", + link: "https://sentry.io/orgredirect/organizations/:orgslug/explore/traces/", + linkText: "Open Traces", + docsLink: "/platforms/javascript/guides/nextjs/tracing/", + docsLinkText: "Learn more", + optionId: "performance", }, ]} /> @@ -119,12 +112,10 @@ After the wizard completes, verify that Sentry is working correctly by going thr You've successfully integrated Sentry into your Next.js application! Here's what to explore next: -- [Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) - Configure Sentry manually or customize your setup -- [Capturing Errors](/platforms/javascript/guides/nextjs/usage/) - Learn how to manually capture errors -- [Session Replay](/platforms/javascript/guides/nextjs/session-replay/) - Configure video-like reproductions of user sessions -- [Tracing](/platforms/javascript/guides/nextjs/tracing/) - Set up distributed tracing across your application -- [Logs](/platforms/javascript/guides/nextjs/logs/) - Send structured logs to Sentry -- [Configuration Options](/platforms/javascript/guides/nextjs/configuration/) - Customize your Sentry configuration +- [Source Maps](/platforms/javascript/guides/nextjs/sourcemaps/) - Upload source maps to see original source code in stack traces +- [Metrics](/platforms/javascript/guides/nextjs/metrics/) - Track custom metrics and monitor application performance +- [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/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 7f28c6f36daea..9f82c68d246ec 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -1,6 +1,6 @@ 'use client'; -import {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useRef, useState} from 'react'; import {ArrowRightIcon, CheckIcon} from '@radix-ui/react-icons'; import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent'; @@ -11,8 +11,14 @@ type ChecklistItem = { id: string; label: string; description?: string; + /** Secondary link (usually to docs) */ + docsLink?: string; + docsLinkText?: string; + /** Primary link (usually to Sentry UI) */ link?: string; linkText?: string; + /** Onboarding option ID - item will be hidden when this option is unchecked */ + optionId?: string; }; type Props = { @@ -50,6 +56,10 @@ export function VerificationChecklist({ }: Props) { const [checkedItems, setCheckedItems] = useState>({}); const [mounted, setMounted] = useState(false); + const [visibleItemIds, setVisibleItemIds] = useState>( + new Set(items.map(item => item.id)) + ); + const listRef = useRef(null); const {emit} = usePlausibleEvent(); // Load checked items from localStorage on mount @@ -77,9 +87,58 @@ export function VerificationChecklist({ } }, [checkedItems, checklistId, mounted]); - const completedCount = Object.values(checkedItems).filter(Boolean).length; - const totalCount = items.length; - const allComplete = completedCount === totalCount; + // Watch for visibility changes on items with data-onboarding-option + useEffect(() => { + if (!listRef.current) { + return undefined; + } + + const updateVisibleItems = () => { + const newVisibleIds = new Set(); + items.forEach(item => { + if (!item.optionId) { + // Items without optionId are always visible + newVisibleIds.add(item.id); + } else { + // Check if the item element is hidden + const element = listRef.current?.querySelector(`[data-item-id="${item.id}"]`); + if (element && !element.classList.contains('hidden')) { + newVisibleIds.add(item.id); + } + } + }); + setVisibleItemIds(newVisibleIds); + }; + + // Initial check + updateVisibleItems(); + + // Set up MutationObserver to watch for class changes + const observer = new MutationObserver(mutations => { + const hasRelevantChange = mutations.some( + mutation => + mutation.type === 'attributes' && + mutation.attributeName === 'class' && + (mutation.target as HTMLElement).hasAttribute('data-onboarding-option') + ); + if (hasRelevantChange) { + updateVisibleItems(); + } + }); + + observer.observe(listRef.current, { + attributes: true, + attributeFilter: ['class'], + subtree: true, + }); + + return () => observer.disconnect(); + }, [items, mounted]); + + const visibleItems = items.filter(item => visibleItemIds.has(item.id)); + const completedCount = visibleItems.filter(item => checkedItems[item.id]).length; + const totalCount = visibleItems.length; + const allComplete = completedCount === totalCount && totalCount > 0; const toggleItem = useCallback( (itemId: string, itemLabel: string) => { @@ -90,21 +149,21 @@ export function VerificationChecklist({ // Emit event for checking/unchecking item emit('Checklist Item Toggle', { props: { - page: window.location.pathname, + checked: newChecked, checklistId, itemId, itemLabel, - checked: newChecked, + page: window.location.pathname, }, }); - // Check if all items are now complete - const newCompletedCount = Object.values(newState).filter(Boolean).length; - if (newCompletedCount === totalCount && newChecked) { + // Check if all visible items are now complete + const newCompletedCount = visibleItems.filter(item => newState[item.id]).length; + if (newCompletedCount === visibleItems.length && newChecked) { emit('Checklist Complete', { props: { - page: window.location.pathname, checklistId, + page: window.location.pathname, }, }); } @@ -112,18 +171,18 @@ export function VerificationChecklist({ return newState; }); }, - [checklistId, emit, totalCount] + [checklistId, emit, visibleItems] ); const handleLinkClick = useCallback( (itemId: string, linkText: string, link: string) => { emit('Checklist Link Click', { props: { - page: window.location.pathname, checklistId, itemId, - linkText, link, + linkText, + page: window.location.pathname, }, }); }, @@ -136,7 +195,7 @@ export function VerificationChecklist({
0 ? (completedCount / totalCount) * 100 : 0}%`}} />
@@ -144,11 +203,16 @@ export function VerificationChecklist({
-
); } diff --git a/src/components/verificationChecklist/style.module.scss b/src/components/verificationChecklist/style.module.scss index 945bdf2ce9f37..0ea83bc5c8a6e 100644 --- a/src/components/verificationChecklist/style.module.scss +++ b/src/components/verificationChecklist/style.module.scss @@ -44,10 +44,6 @@ } .item { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 1rem; padding: 0.75rem; border-radius: 0.375rem; background: var(--white); @@ -58,13 +54,35 @@ border-color: var(--gray-200); } - // Mobile: stack content and link vertically + &.hasContent { + // Items with expandable content + } +} + +.itemHeader { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + + // Mobile: stack content and actions vertically @media (max-width: 640px) { flex-direction: column; gap: 0.5rem; } } +.itemActions { + display: flex; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; + + @media (max-width: 640px) { + margin-left: 2rem; // Align with text content + } +} + .label { display: flex; align-items: flex-start; @@ -163,18 +181,11 @@ color: var(--gray-500); text-decoration: none; white-space: nowrap; - flex-shrink: 0; - align-self: center; &:hover { text-decoration: underline; color: var(--gray-400); } - - // Mobile: align docs link to the left - @media (max-width: 640px) { - margin-left: 2rem; // Align with text content (checkbox width + gap) - } } .arrowIcon { @@ -182,6 +193,58 @@ height: 14px; } +.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 { + margin-top: 0.75rem; + margin-left: 2rem; // Align with label text + padding: 0.75rem; + background: var(--gray-50); + border-radius: 0.375rem; + border: 1px solid var(--gray-100); + + // Reset some MDX component styles within expanded content + :global { + pre { + margin: 0; + } + + p:last-child { + margin-bottom: 0; + } + } +} + .successMessage { display: flex; align-items: center; @@ -200,6 +263,24 @@ 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 { @@ -262,9 +343,41 @@ } } + .expandButton { + background: #231c3d; + border-color: #3e3446; + + &:hover { + background: #2d2447; + border-color: #584774; + } + } + + .chevronIcon { + color: #9481a4; + } + + .expandedContent { + background: #1a1523; + border-color: #3e3446; + } + .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; + } + } + } } From d70148fbb5b63c4c42ac41358f7c8f216a1bf550 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Thu, 11 Dec 2025 19:14:12 -0500 Subject: [PATCH 11/14] fix lint --- src/components/verificationChecklist/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 9407de4a256af..230629a90143d 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -10,6 +10,8 @@ import styles from './style.module.scss'; type ChecklistItem = { id: string; label: string; + /** Expandable content (code blocks, additional details) */ + content?: ReactNode; description?: string; /** Secondary link (usually to docs) */ docsLink?: string; @@ -19,8 +21,6 @@ type ChecklistItem = { linkText?: string; /** Onboarding option ID - item will be hidden when this option is unchecked */ optionId?: string; - /** Expandable content (code blocks, additional details) */ - content?: ReactNode; }; type Props = { From afbda3d3c18dc10762571082187d80892cac1b71 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 00:16:43 +0000 Subject: [PATCH 12/14] [getsentry/action-github-commit] Auto commit --- src/components/verificationChecklist/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 230629a90143d..4ecfe63ecb2cf 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -258,7 +258,9 @@ export function VerificationChecklist({ href={item.link} target={item.link.startsWith('http') ? '_blank' : undefined} rel={ - item.link.startsWith('http') ? 'noopener noreferrer' : undefined + item.link.startsWith('http') + ? 'noopener noreferrer' + : undefined } className={styles.link} onClick={e => { @@ -318,8 +320,7 @@ export function VerificationChecklist({ )}
- Something not working?{' '} - Check troubleshooting + Something not working? Check troubleshooting {' · '} Get support
From 764533c6a46bbecca73156b16d199e297212e35b Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Fri, 12 Dec 2025 17:19:10 -0500 Subject: [PATCH 13/14] updated onboarding checklist --- .../javascript/guides/nextjs/index.mdx | 340 +++++---------- .../verificationChecklist/index.tsx | 407 +++++++++++++----- .../verificationChecklist/style.module.scss | 210 +++++---- src/mdxComponents.ts | 3 +- 4 files changed, 546 insertions(+), 414 deletions(-) diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index 3fb893601caea..f94716c8371eb 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -35,9 +35,9 @@ It automatically creates and configures the following files: | File | Purpose | | ----------------------------- | --------------------------------------------------------- | -| `sentry.server.config.ts` | Server-side SDK initialization | +| `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-client.ts` | Client-side SDK initialization | | `instrumentation.ts` | Next.js instrumentation hook | | `next.config.ts` | Updated with Sentry configuration | | `global-error.tsx` | App Router error boundary | @@ -45,34 +45,56 @@ It automatically creates and configures the following files: | `app/api/sentry-example-api/` | Example API route | | `.env.sentry-build-plugin` | Auth token for source map uploads (added to `.gitignore`) | -Here's what the instrumentation files look like: +**Why multiple configuration files?** Next.js runs code in different environments, each requiring separate Sentry initialization: + +- **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) + +
+ +Prefer to set things up yourself? Check out the [Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) guide. + +## Verify + +Select the features you enabled in the wizard, then work through the checklist to verify everything is working: + + + + + + + +The wizard created these files. Check they exist and contain your DSN: ```typescript {tabTitle:Client} {filename:instrumentation-client.ts} import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", - - // Add optional integrations for additional features - integrations: [Sentry.replayIntegration()], - - // Define how likely traces are sampled. Adjust this value in production. + // ___PRODUCT_OPTION_START___ performance tracesSampleRate: 1.0, - - // Define how likely Replay events are sampled. + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ session-replay replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: false, - - // Enable Sentry Logs - _experiments: { - enableLogs: true, - }, + // ___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 + ], }); - -export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; ``` ```typescript {tabTitle:Server} {filename:sentry.server.config.ts} @@ -80,17 +102,12 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", - - // Define how likely traces are sampled. Adjust this value in production. + // ___PRODUCT_OPTION_START___ performance tracesSampleRate: 1.0, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: false, - - // Enable Sentry Logs - _experiments: { - enableLogs: true, - }, + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ logs + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs }); ``` @@ -99,213 +116,56 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", - - // Define how likely traces are sampled. Adjust this value in production. + // ___PRODUCT_OPTION_START___ performance tracesSampleRate: 1.0, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: false, + // ___PRODUCT_OPTION_END___ performance }); ``` -```typescript {tabTitle:Server Instrumentation} {filename:instrumentation.ts} -import * as Sentry from "@sentry/nextjs"; - -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"); - } -} - -export const onRequestError = Sentry.captureRequestError; -``` - - - -Prefer to set things up yourself? Check out the [Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) guide. - -## Verify - -Select the features you enabled in the wizard, then go through the checklist to verify your setup: + + +Visit `/sentry-example-page` in your browser and click the "Throw Sample Error" button. - - - - -If you skipped creating the example page during wizard setup, add this button to any page to test error capturing: +If you don't have an example page, add this button to any page: ```tsx -import * as Sentry from "@sentry/nextjs"; - -; -``` - -Or create the full example page manually: - -```tsx {filename:app/sentry-example-page/page.tsx} "use client"; - import * as Sentry from "@sentry/nextjs"; -export default function SentryExamplePage() { +export function TestButton() { return ( -
-

Sentry Example Page

-

Click the button to trigger a test error:

- -
+ ); } ``` -```ts {filename:app/api/sentry-example-api/route.ts} -import * as Sentry from "@sentry/nextjs"; -import { NextResponse } from "next/server"; - -export const dynamic = "force-dynamic"; - -export function GET() { - Sentry.logger.info("API route called", { route: "/api/sentry-example-api" }); - Sentry.logger.error("About to throw an API error"); - - throw new Error("Sentry Example API Error"); - return NextResponse.json({ data: "Testing Sentry..." }); -} -``` - -
- - - - + - - - -**Capture an exception manually:** - -```typescript -import * as Sentry from "@sentry/nextjs"; - -try { - riskyOperation(); -} catch (error) { - Sentry.captureException(error); -} -``` - -**Create a custom span for tracing:** + +You can also create custom spans to measure specific operations: ```typescript await Sentry.startSpan({ name: "my-operation", op: "task" }, async () => { @@ -313,21 +173,37 @@ await Sentry.startSpan({ name: "my-operation", op: "task" }, async () => { }); ``` -**Send structured logs:** + + + +Send structured logs from anywhere in your application: ```typescript -Sentry.logger.info("User completed checkout", { - cartId: "abc123", - total: 99.99, -}); -Sentry.logger.warn("Slow API response", { - endpoint: "/api/data", - duration: 5000, -}); -Sentry.logger.error("Payment failed", { reason: "insufficient_funds" }); +Sentry.logger.info("User action", { userId: "123" }); +Sentry.logger.warn("Slow response", { duration: 5000 }); +Sentry.logger.error("Operation failed", { reason: "timeout" }); ``` - + + ## Next Steps diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 4ecfe63ecb2cf..2dc66d1e975ea 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -1,17 +1,26 @@ 'use client'; -import {ReactNode, useCallback, useEffect, useRef, useState} from 'react'; +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 ChecklistItem = { +type ChecklistItemProps = { id: string; label: string; - /** Expandable content (code blocks, additional details) */ - content?: ReactNode; + children?: ReactNode; description?: string; /** Secondary link (usually to docs) */ docsLink?: string; @@ -19,56 +28,134 @@ type ChecklistItem = { /** Primary link (usually to Sentry UI) */ link?: string; linkText?: string; - /** Onboarding option ID - item will be hidden when this option is unchecked */ + /** 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; - /** Items to display in the checklist */ - items?: ChecklistItem[]; + 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; }; -const DEFAULT_ITEMS: ChecklistItem[] = [ - { - id: 'trigger-error', - label: 'Trigger a test error', - description: 'Use the example code or page to generate an error', - }, - { - id: 'see-error', - label: 'See the error in Sentry', - description: "Check your project's Issues page", - }, - { - id: 'run-build', - label: 'Run a production build', - description: 'Verify source maps are uploaded', - }, -]; - function getStorageKey(checklistId: string): string { return `sentry-docs-checklist-${checklistId}`; } export function VerificationChecklist({ + children, checklistId = 'default', - items = DEFAULT_ITEMS, + 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 [visibleItemIds, setVisibleItemIds] = useState>( - new Set(items.map(item => item.id)) - ); + const [optionalItemIds, setOptionalItemIds] = useState>(new Set()); const listRef = useRef(null); const {emit} = usePlausibleEvent(); - // Load checked items from localStorage on mount + // 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 { @@ -79,7 +166,12 @@ export function VerificationChecklist({ } catch { // Ignore localStorage errors } - }, [checklistId]); + + // 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(() => { @@ -93,57 +185,88 @@ export function VerificationChecklist({ } }, [checkedItems, checklistId, mounted]); - // Watch for visibility changes on items with data-onboarding-option + // Watch for which onboarding options are enabled/disabled + // We watch the OnboardingOptionButtons checkboxes to determine which options are enabled useEffect(() => { - if (!listRef.current) { + if (!mounted) { return undefined; } - const updateVisibleItems = () => { - const newVisibleIds = new Set(); + const updateOptionalItems = () => { + const newOptionalIds = new Set(); + const onboardingContainer = document.querySelector('.onboarding-options'); + items.forEach(item => { - if (!item.optionId) { - // Items without optionId are always visible - newVisibleIds.add(item.id); - } else { - // Check if the item element is hidden - const element = listRef.current?.querySelector(`[data-item-id="${item.id}"]`); - if (element && !element.classList.contains('hidden')) { - newVisibleIds.add(item.id); + 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); } } }); - setVisibleItemIds(newVisibleIds); + setOptionalItemIds(newOptionalIds); }; - // Initial check - updateVisibleItems(); - - // Set up MutationObserver to watch for class changes - const observer = new MutationObserver(mutations => { - const hasRelevantChange = mutations.some( - mutation => - mutation.type === 'attributes' && - mutation.attributeName === 'class' && - (mutation.target as HTMLElement).hasAttribute('data-onboarding-option') - ); - if (hasRelevantChange) { - updateVisibleItems(); - } + // 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(); }); - observer.observe(listRef.current, { + // Watch the document for data-state attribute changes on checkboxes + observer.observe(document.body, { attributes: true, - attributeFilter: ['class'], + attributeFilter: ['data-state'], subtree: true, }); - return () => observer.disconnect(); + // 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]); - const visibleItems = items.filter(item => visibleItemIds.has(item.id)); - const completedCount = visibleItems.filter(item => checkedItems[item.id]).length; - const totalCount = visibleItems.length; + // 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( @@ -163,9 +286,27 @@ export function VerificationChecklist({ }, }); - // Check if all visible items are now complete - const newCompletedCount = visibleItems.filter(item => newState[item.id]).length; - if (newCompletedCount === visibleItems.length && newChecked) { + // 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, @@ -177,7 +318,7 @@ export function VerificationChecklist({ return newState; }); }, - [checklistId, emit, visibleItems] + [checklistId, emit, items, sequential] ); const toggleExpanded = useCallback((itemId: string) => { @@ -199,6 +340,34 @@ export function VerificationChecklist({ [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 (
@@ -218,94 +387,114 @@ export function VerificationChecklist({
    {items.map(item => { const isChecked = checkedItems[item.id] || false; - const isExpanded = expandedItems[item.id] || false; - const hasContent = !!item.content; + 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 (
  • -
    -
  • ); diff --git a/src/components/verificationChecklist/style.module.scss b/src/components/verificationChecklist/style.module.scss index 0ea83bc5c8a6e..9cc6164d72c78 100644 --- a/src/components/verificationChecklist/style.module.scss +++ b/src/components/verificationChecklist/style.module.scss @@ -44,55 +44,58 @@ } .item { - padding: 0.75rem; border-radius: 0.375rem; background: var(--white); border: 1px solid var(--gray-100); - transition: border-color 0.15s ease, background-color 0.15s ease; + transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease; + overflow: hidden; &:hover { border-color: var(--gray-200); } - &.hasContent { - // Items with expandable content + &.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: flex-start; + align-items: center; justify-content: space-between; gap: 1rem; + padding: 0.75rem; + user-select: none; - // Mobile: stack content and actions vertically @media (max-width: 640px) { - flex-direction: column; + flex-wrap: wrap; gap: 0.5rem; } } -.itemActions { +.headerLeft { display: flex; - align-items: center; + align-items: flex-start; gap: 0.75rem; - flex-shrink: 0; - - @media (max-width: 640px) { - margin-left: 2rem; // Align with text content - } + flex: 1; + min-width: 0; } -.label { +.headerRight { display: flex; - align-items: flex-start; + align-items: center; gap: 0.75rem; - cursor: pointer; - flex: 1; - min-width: 0; // Allow text to wrap + flex-shrink: 0; - &.checked { - opacity: 0.7; + @media (max-width: 640px) { + margin-left: auto; } } @@ -119,6 +122,11 @@ border-radius: 4px; background: var(--white); transition: all 0.15s ease; + cursor: pointer; + + &:hover { + border-color: #6c5fc7; + } &.checked { background: #6c5fc7; @@ -132,17 +140,22 @@ color: white; } -.content { +.labelContainer { display: flex; flex-direction: column; gap: 0.125rem; - min-width: 0; // Allow text to wrap + 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; @@ -150,30 +163,13 @@ } } -.descriptionRow { - display: flex; - align-items: baseline; - flex-wrap: wrap; - gap: 0.5rem; -} - -.description { - font-size: 0.875rem; - color: var(--gray-500); -} - -.link { - display: inline-flex; - align-items: center; - gap: 0.25rem; - font-size: 0.875rem; - color: #6c5fc7; - text-decoration: none; - white-space: nowrap; - - &:hover { - text-decoration: underline; - } +.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 { @@ -184,15 +180,10 @@ &:hover { text-decoration: underline; - color: var(--gray-400); + color: var(--gray-600); } } -.arrowIcon { - width: 14px; - height: 14px; -} - .expandButton { display: flex; align-items: center; @@ -226,20 +217,87 @@ } .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; - margin-left: 2rem; // Align with label text - padding: 0.75rem; - background: var(--gray-50); - border-radius: 0.375rem; - border: 1px solid var(--gray-100); - // Reset some MDX component styles within expanded content - :global { - pre { - margin: 0; - } + // Reset some MDX component styles within content + > :global(p:first-child) { + margin-top: 0; + } - p:last-child { + > :global(*:last-child) { + margin-bottom: 0; + } + + // Code blocks get proper spacing + :global(pre) { + margin: 0.5rem 0; + + &:last-child { margin-bottom: 0; } } @@ -295,12 +353,20 @@ &:hover { border-color: #584774; } + + &.expanded { + border-color: #584774; + } } .customCheckbox { background: #231c3d; border-color: #584774; + &:hover { + border-color: #8b7fd9; + } + &.checked { background: #8b7fd9; border-color: #8b7fd9; @@ -315,8 +381,13 @@ } } - .description { - color: #a796b4; + .optionalBadge { + background: #3e3446; + color: #9481a4; + } + + .expandedDescription { + color: #bbadc6; } .progressBar { @@ -327,7 +398,7 @@ color: #bbadc6; } - .link { + .primaryLink { color: #a796f0; &:hover { @@ -357,11 +428,6 @@ color: #9481a4; } - .expandedContent { - background: #1a1523; - border-color: #3e3446; - } - .successMessage { background: rgba(45, 106, 79, 0.3); border-color: #40916c; diff --git a/src/mdxComponents.ts b/src/mdxComponents.ts index c20b02d774b2e..e09e52ef3a521 100644 --- a/src/mdxComponents.ts +++ b/src/mdxComponents.ts @@ -55,7 +55,7 @@ import { } from './components/splitLayout'; import {StepComponent, StepConnector} from './components/stepConnector'; import {TableOfContents} from './components/tableOfContents'; -import {VerificationChecklist} from './components/verificationChecklist'; +import {ChecklistItem, VerificationChecklist} from './components/verificationChecklist'; import {VersionRequirement} from './components/version-requirement'; import {VimeoEmbed} from './components/video'; @@ -118,6 +118,7 @@ export function mdxComponents( StepComponent, StepConnector, VerificationChecklist, + ChecklistItem, VimeoEmbed, VersionRequirement, a: SmartLink, From 27475a87aa11d8a422f173c2797d15d6caaa32c0 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 22:20:25 +0000 Subject: [PATCH 14/14] [getsentry/action-github-commit] Auto commit --- src/components/verificationChecklist/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 2dc66d1e975ea..3c05b569f241d 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -44,8 +44,9 @@ function ChecklistItemComponent(_props: ChecklistItemProps) { } ChecklistItemComponent.displayName = 'ChecklistItem'; // Static marker for identification -(ChecklistItemComponent as unknown as {__checklistItemMarker: symbol}).__checklistItemMarker = - CHECKLIST_ITEM_MARKER; +( + ChecklistItemComponent as unknown as {__checklistItemMarker: symbol} +).__checklistItemMarker = CHECKLIST_ITEM_MARKER; export const ChecklistItem = ChecklistItemComponent; @@ -200,7 +201,9 @@ export function VerificationChecklist({ 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"]'); + const checkboxes = onboardingContainer.querySelectorAll( + 'button[role="checkbox"]' + ); // Map option IDs to their display names const optionNameMap: Record = {