From 65dbcd2d2cd73aa87c815a2e73b8993ed47ffb1c Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Tue, 9 Dec 2025 11:28:31 -0500 Subject: [PATCH 01/27] 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 00000000000000..a793e7662d7fde --- /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 ecbbfc013d52bb..f04cef3df03515 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 3e949a48199d19..e874e34619fadd 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 00000000000000..8a5c016e19ebe9 --- /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 00000000000000..5e6e00a8ade380 --- /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 00000000000000..7f2583720767b2 --- /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/27] 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 e874e34619fadd..4647fa7494121d 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 ecbbfc013d52bb..e81313e12c00d8 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 4647fa7494121d..00000000000000 --- 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 00000000000000..74fa5468e15c55 --- /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 00000000000000..9a64a206c8d7bb --- /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 00000000000000..241578a7594264 --- /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 00000000000000..bcb8b057801267 --- /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 049fefcbbcf1e8..9612599cd1e75d 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 00000000000000..0fddb5cc89ae0b --- /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 00000000000000..3c74f83b8d82fa --- /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 a899f5426facb6..13ec4829068be3 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 c6458a492276c6..c20b02d774b2e3 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/27] [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 0fddb5cc89ae0b..7f28c6f36daeae 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/27] 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 e81313e12c00d8..62263329121249 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 7f28c6f36daeae..9f82c68d246ec6 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 945bdf2ce9f371..0ea83bc5c8a6ed 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/27] 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 9407de4a256afe..230629a90143da 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/27] [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 230629a90143da..4ecfe63ecb2cf7 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/27] 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 3fb893601caea3..f94716c8371ebe 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 4ecfe63ecb2cf7..2dc66d1e975ea1 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 0ea83bc5c8a6ed..9cc6164d72c783 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 c20b02d774b2e3..e09e52ef3a5216 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/27] [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 2dc66d1e975ea1..3c05b569f241d2 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 = { From 679f4fc5e1beae4a80db3719e7456a7e8703ba71 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Mon, 15 Dec 2025 14:17:38 -0500 Subject: [PATCH 15/27] updated quickstart --- .../common/troubleshooting/index.mdx | 53 +++ .../javascript/guides/nextjs/index.mdx | 309 ++++++++++++------ .../verificationChecklist/index.tsx | 9 - .../verificationChecklist/style.module.scss | 31 -- 4 files changed, 261 insertions(+), 141 deletions(-) diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index f32c8bda4e3807..8967d300652a84 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -38,6 +38,59 @@ If you set up the Sentry SDK and it's not sending any data to Sentry: + + + + +Use this checklist to systematically verify your Next.js Sentry setup: + + + + + + + + + + + + + + + + If you update your Sentry SDK to a new major version, you might encounter breaking changes that need some adaption on your end. diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index f94716c8371ebe..03ac3fe420420a 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -23,41 +23,7 @@ Run the Sentry wizard to automatically configure Sentry in your Next.js applicat npx @sentry/wizard@latest -i nextjs ``` - - -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/)) - -It automatically creates and configures the following files: - -| File | Purpose | -| ----------------------------- | --------------------------------------------------------- | -| `instrumentation-client.ts` | Client-side SDK initialization (browser) | -| `sentry.server.config.ts` | Server-side SDK initialization (Node.js) | -| `sentry.edge.config.ts` | Edge runtime SDK initialization | -| `instrumentation.ts` | Next.js instrumentation hook | -| `next.config.ts` | Updated with Sentry configuration | -| `global-error.tsx` | App Router error boundary | -| `app/sentry-example-page/` | Example page to test your setup | -| `app/api/sentry-example-api/` | Example API route | -| `.env.sentry-build-plugin` | Auth token for source map uploads (added to `.gitignore`) | - -**Why multiple configuration files?** Next.js runs code in different environments, each requiring separate Sentry initialization: - -- **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 will prompt you to select features. Choose the ones you want to enable: - +Prefer to set things up yourself? Check out the [Manual Setup](/platforms/javascript/guides/nextjs/manual-setup/) guide. + +## What the Wizard Created + +The wizard configured Sentry for all Next.js runtime environments and created files to test your setup. + + + + + + +### SDK Initialization - - -The wizard created these files. Check they exist and contain your DSN: +Next.js runs code in different environments. The wizard creates separate initialization files for each: + +- **Client** (`instrumentation-client.ts`) — Runs in the browser +- **Server** (`sentry.server.config.ts`) — Runs in Node.js +- **Edge** (`sentry.edge.config.ts`) — Runs in edge runtimes + + + ```typescript {tabTitle:Client} {filename:instrumentation-client.ts} import * as Sentry from "@sentry/nextjs"; @@ -122,79 +104,195 @@ Sentry.init({ }); ``` - - -Visit `/sentry-example-page` in your browser and click the "Throw Sample Error" button. + + + + + -If you don't have an example page, add this button to any page: +### Server-Side Registration -```tsx +The `instrumentation.ts` file registers your server and edge configurations with Next.js. + + + + +```typescript {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; +``` + + + + + + + +### Next.js Configuration + +Your `next.config.ts` is wrapped with `withSentryConfig` to enable source map uploads, tunneling (to avoid ad-blockers), and other build-time features. + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + org: "___ORG_SLUG___", + project: "___PROJECT_SLUG___", + + // Upload source maps for readable stack traces + authToken: process.env.SENTRY_AUTH_TOKEN, + + // Route Sentry requests through your server (avoids ad-blockers) + tunnelRoute: "/monitoring-tunnel", + + silent: !process.env.CI, +}); +``` + + + + + + + +### Error Handling + +The wizard creates `app/global-error.tsx` to capture React rendering errors in your App Router application. + + + + +```tsx {filename:app/global-error.tsx} "use client"; + import * as Sentry from "@sentry/nextjs"; +import { useEffect } from "react"; + +export default function GlobalError({ + error, +}: { + error: Error & { digest?: string }; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); -export function TestButton() { return ( - + + +

    Something went wrong!

    + + ); } ``` - - - -You can also create custom spans to measure specific operations: +
    +
    -```typescript -await Sentry.startSpan({ name: "my-operation", op: "task" }, async () => { - await doSomething(); -}); + + + +### Source Maps + +The wizard creates `.env.sentry-build-plugin` with your auth token for source map uploads. This file is automatically added to `.gitignore`. + +For CI/CD, set the `SENTRY_AUTH_TOKEN` environment variable. + + + + +```bash {filename:.env.sentry-build-plugin} +SENTRY_AUTH_TOKEN=sntrys_eyJ... ``` - - - -Send structured logs from anywhere in your application: +```bash {filename:CI/CD Environment} +SENTRY_AUTH_TOKEN=sntrys_eyJ... +``` + + + + + + + +### Example Page + +The wizard creates `/sentry-example-page` with a button that triggers a test error. Use this to verify your setup. + + + + +``` +app/ +├── sentry-example-page/ +│ └── page.tsx # Test page with error button +└── api/ + └── sentry-example-api/ + └── route.ts # Test API route +``` + + + + +
    + +## Verify Your Setup + + + +The example page tests all your enabled features with a single action: + +1. Start your dev server: + +```bash +npm run dev +``` + +2. Visit [localhost:3000/sentry-example-page](http://localhost:3000/sentry-example-page) + +3. Click **"Throw Sample Error"** + +4. Check your data in Sentry: + +**Errors** — [Open Issues](https://sentry.io/orgredirect/organizations/:orgslug/issues/) + +You should see "This is a test error" with a full stack trace pointing to your source code. + + + +**Tracing** — [Open Traces](https://sentry.io/orgredirect/organizations/:orgslug/explore/traces/) + +You should see the page load trace and the button click span. Learn more about [custom spans](/platforms/javascript/guides/nextjs/tracing/instrumentation/custom-instrumentation/). + + + + + +**Session Replay** — [Open Replays](https://sentry.io/orgredirect/organizations/:orgslug/replays/) + +Watch a video-like recording of your session, including the moment the error occurred. Learn more about [Session Replay configuration](/platforms/javascript/guides/nextjs/session-replay/). + + + + + +**Logs** — [Open Logs](https://sentry.io/orgredirect/organizations/:orgslug/explore/logs/) + +See structured log entries from your application. You can send logs from anywhere: ```typescript Sentry.logger.info("User action", { userId: "123" }); @@ -202,8 +300,17 @@ Sentry.logger.warn("Slow response", { duration: 5000 }); Sentry.logger.error("Operation failed", { reason: "timeout" }); ``` - - +Learn more about [Logs configuration](/platforms/javascript/guides/nextjs/logs/). + + + + + +- If you encountered issues with our installation wizard, try [setting up Sentry manually](/platforms/javascript/guides/nextjs/manual-setup/) +- Check [Troubleshooting](/platforms/javascript/guides/nextjs/troubleshooting/) for common issues +- [Get support](https://sentry.io/support/) + + ## Next Steps diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 2dc66d1e975ea1..a4786c8d6aa3b9 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -57,8 +57,6 @@ type Props = { collapsible?: boolean; /** Auto-expand next item when current is checked (default: true) */ sequential?: boolean; - /** Troubleshooting link URL */ - troubleshootingLink?: string; }; function getStorageKey(checklistId: string): string { @@ -70,7 +68,6 @@ export function VerificationChecklist({ checklistId = 'default', collapsible = true, sequential = true, - troubleshootingLink = '/platforms/javascript/guides/nextjs/troubleshooting/', }: Props) { const [checkedItems, setCheckedItems] = useState>({}); const [expandedItems, setExpandedItems] = useState>({}); @@ -507,12 +504,6 @@ export function VerificationChecklist({ All done! Sentry is successfully configured.
)} - -
- Something not working? Check troubleshooting - {' · '} - Get support -
); } diff --git a/src/components/verificationChecklist/style.module.scss b/src/components/verificationChecklist/style.module.scss index 9cc6164d72c783..879bc1082aa571 100644 --- a/src/components/verificationChecklist/style.module.scss +++ b/src/components/verificationChecklist/style.module.scss @@ -321,24 +321,6 @@ 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 { @@ -433,17 +415,4 @@ border-color: #40916c; color: #69db7c; } - - .troubleshooting { - border-color: #3e3446; - color: #9481a4; - - a { - color: #a796f0; - - &:hover { - color: #c4baff; - } - } - } } From ebb720405c63c41a6450438823cbe1f0862d010a Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Mon, 15 Dec 2025 15:15:15 -0500 Subject: [PATCH 16/27] plausible events --- .../javascript/guides/nextjs/index.mdx | 12 +++++++--- .../guides/nextjs/manual-setup/index.mdx | 24 ++++++++++++------- .../nextjs/manual-setup/pages-router.mdx | 24 ++++++++++++------- src/components/onboarding/index.tsx | 14 +++++++++++ src/hooks/usePlausibleEvent.tsx | 6 +++++ 5 files changed, 59 insertions(+), 21 deletions(-) diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index 03ac3fe420420a..66bd87dcac5798 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -62,7 +62,7 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", // ___PRODUCT_OPTION_START___ performance - tracesSampleRate: 1.0, + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance // ___PRODUCT_OPTION_START___ session-replay replaysSessionSampleRate: 0.1, @@ -85,7 +85,7 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", // ___PRODUCT_OPTION_START___ performance - tracesSampleRate: 1.0, + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance // ___PRODUCT_OPTION_START___ logs enableLogs: true, @@ -99,11 +99,17 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", // ___PRODUCT_OPTION_START___ performance - tracesSampleRate: 1.0, + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance }); ``` + + +The example above samples 100% of traces in development and 10% in production. Monitor your [usage stats](https://sentry.io/orgredirect/organizations/:orgslug/settings/stats/?dataCategory=spans) and adjust `tracesSampleRate` based on your traffic volume. Learn more about [sampling configuration](/platforms/javascript/guides/nextjs/configuration/sampling/). + + + diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx index 454eb8d7deab5a..3277c643f52622 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx @@ -139,9 +139,9 @@ Sentry.init({ 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, + // Capture 100% in dev, 10% in production + // Adjust based on your traffic volume + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance integrations: [ // ___PRODUCT_OPTION_START___ session-replay @@ -183,9 +183,9 @@ Sentry.init({ 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, + // Capture 100% in dev, 10% in production + // Adjust based on your traffic volume + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance // ___PRODUCT_OPTION_START___ logs @@ -205,9 +205,9 @@ Sentry.init({ 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, + // Capture 100% in dev, 10% in production + // Adjust based on your traffic volume + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance // ___PRODUCT_OPTION_START___ logs @@ -217,6 +217,12 @@ Sentry.init({ }); ``` + + +The example above samples 100% of traces in development and 10% in production. Monitor your [usage stats](https://sentry.io/orgredirect/organizations/:orgslug/settings/stats/?dataCategory=spans) and adjust `tracesSampleRate` based on your traffic volume. Learn more about [sampling configuration](/platforms/javascript/guides/nextjs/configuration/sampling/). + + + diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx index 164643228f27cf..4143806f32bbc9 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx @@ -111,9 +111,9 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", - // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. - // We recommend adjusting this value in production - tracesSampleRate: 1.0, + // Capture 100% in dev, 10% in production + // Adjust based on your traffic volume + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // Capture Replay for 10% of all sessions, // plus for 100% of sessions with an error @@ -130,9 +130,9 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", - // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. - // We recommend adjusting this value in production - tracesSampleRate: 1.0, + // Capture 100% in dev, 10% in production + // Adjust based on your traffic volume + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, }); ``` @@ -142,12 +142,18 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", - // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. - // We recommend adjusting this value in production - tracesSampleRate: 1.0, + // Capture 100% in dev, 10% in production + // Adjust based on your traffic volume + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, }); ``` + + +Monitor your [usage stats](https://sentry.io/orgredirect/organizations/:orgslug/settings/stats/?dataCategory=spans) and adjust `tracesSampleRate` based on your traffic volume. Learn more about [sampling configuration](/platforms/javascript/guides/nextjs/configuration/sampling/). + + + diff --git a/src/components/onboarding/index.tsx b/src/components/onboarding/index.tsx index 7e8e00a689594f..41d440f0880c7e 100644 --- a/src/components/onboarding/index.tsx +++ b/src/components/onboarding/index.tsx @@ -6,6 +6,8 @@ import {QuestionMarkCircledIcon} from '@radix-ui/react-icons'; import * as Tooltip from '@radix-ui/react-tooltip'; import {Button, Checkbox, Theme} from '@radix-ui/themes'; +import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent'; + import styles from './styles.module.scss'; import {CodeContext} from '../codeContext'; @@ -339,6 +341,7 @@ export function OnboardingOptionButtons({ options: (OnboardingOptionType | OptionId)[]; }) { const codeContext = useContext(CodeContext); + const {emit} = usePlausibleEvent(); const normalizedOptions = initialOptions .map(option => { @@ -374,6 +377,17 @@ export function OnboardingOptionButtons({ function handleCheckedChange(clickedOption: OnboardingOptionType, checked: boolean) { touchOptions(); + + // Track the toggle event in Plausible + emit('Onboarding Option Toggle', { + props: { + checked, + optionId: clickedOption.id, + optionName: optionDetails[clickedOption.id].name, + page: typeof window !== 'undefined' ? window.location.pathname : '', + }, + }); + const dependencies = optionDetails[clickedOption.id].deps ?? []; const depenedants = options.filter(opt => optionDetails[opt.id].deps?.includes(clickedOption.id)) ?? []; diff --git a/src/hooks/usePlausibleEvent.tsx b/src/hooks/usePlausibleEvent.tsx index 13ec4829068be3..8fff6df422a489 100644 --- a/src/hooks/usePlausibleEvent.tsx +++ b/src/hooks/usePlausibleEvent.tsx @@ -41,6 +41,12 @@ type PlausibleEventProps = { helpful: boolean; page: string; }; + ['Onboarding Option Toggle']: { + checked: boolean; + optionId: string; + optionName: string; + page: string; + }; ['Open Expandable']: { page: string; title: string; From fee3e44ae174337875c2a5d60bc760372eeb4837 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Mon, 15 Dec 2025 15:21:42 -0500 Subject: [PATCH 17/27] react component annotation webpack --- .../nextjs/manual-setup/webpack-setup.mdx | 77 +++++++++++++++++-- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx index be37dfe60d5cc9..942e36dd34a57a 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx @@ -14,12 +14,13 @@ This guide covers the configuration differences for **Next.js applications using ## Key Differences: Webpack vs Turbopack -| Feature | Turbopack | Webpack | -| ------------------------------- | ------------------------------- | ----------------------------------- | -| Server function instrumentation | Automatic via Next.js telemetry | Build-time code injection | -| Middleware instrumentation | Automatic via Next.js telemetry | Build-time code injection | -| Source map upload | Post-build via CLI (default) | During build via plugin (default) | -| Route exclusion | Not supported | Supported via `excludeServerRoutes` | +| Feature | Turbopack | Webpack | +| ------------------------------- | ------------------------------- | ---------------------------------------- | +| Server function instrumentation | Automatic via Next.js telemetry | Build-time code injection | +| Middleware instrumentation | Automatic via Next.js telemetry | Build-time code injection | +| Source map upload | Post-build via CLI (default) | During build via plugin (default) | +| Route exclusion | Not supported | Supported via `excludeServerRoutes` | +| React component annotation | Not supported | Supported via `reactComponentAnnotation` | ## Auto-Instrumentation Options @@ -251,6 +252,70 @@ export async function submitForm(formData: FormData) { +## React Component Annotation + +With Webpack, you can enable React component name tracking. This annotates React components with `data-sentry-*` attributes that allow Sentry to identify which components users interacted with in [Session Replay](/platforms/javascript/guides/nextjs/session-replay/) and [breadcrumbs](/platforms/javascript/guides/nextjs/enriching-events/breadcrumbs/). + + + This feature is only available with Webpack. Turbopack does not support React + component annotation. + + + + + + + +### Enable Component Annotation + +Enable `reactComponentAnnotation` to track component names in your application. This is especially useful for debugging user interactions in Session Replay. + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + reactComponentAnnotation: { + enabled: true, + }, +}); +``` + + + + + + + + + + + +### Exclude Specific Components + +If you have components you don't want annotated (for privacy or performance reasons), you can exclude them by name. + + + + +```typescript {filename:next.config.ts} +import { withSentryConfig } from "@sentry/nextjs"; + +export default withSentryConfig(nextConfig, { + reactComponentAnnotation: { + enabled: true, + ignoredComponents: ["SensitiveForm", "InternalDebugPanel"], + }, +}); +``` + + + + + + ## Tunneling with Webpack Tunneling works identically for both Webpack and Turbopack. Sentry automatically filters tunnel requests from middleware spans to prevent noise in your monitoring data. From abab1b5eeaeb4f1334c9ee02fd78fa4be8805398 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Tue, 16 Dec 2025 12:22:34 -0500 Subject: [PATCH 18/27] updates --- .../javascript/guides/nextjs/index.mdx | 16 +++- .../guides/nextjs/manual-setup/index.mdx | 65 ++++---------- .../nextjs/manual-setup/pages-router.mdx | 49 ++++++++++- .../nextjs/manual-setup/webpack-setup.mdx | 65 ++++++++------ .../verificationChecklist/index.tsx | 84 ++++++++++--------- 5 files changed, 157 insertions(+), 122 deletions(-) diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index 66bd87dcac5798..78025f14c442bd 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -61,6 +61,9 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", + + // Adds request headers and IP for users + sendDefaultPii: true, // ___PRODUCT_OPTION_START___ performance tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance @@ -84,6 +87,9 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", + + // Adds request headers and IP for users + sendDefaultPii: true, // ___PRODUCT_OPTION_START___ performance tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance @@ -98,6 +104,9 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", + + // Adds request headers and IP for users + sendDefaultPii: true, // ___PRODUCT_OPTION_START___ performance tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, // ___PRODUCT_OPTION_END___ performance @@ -216,16 +225,17 @@ export default function GlobalError({ The wizard creates `.env.sentry-build-plugin` with your auth token for source map uploads. This file is automatically added to `.gitignore`. -For CI/CD, set the `SENTRY_AUTH_TOKEN` environment variable. +For CI/CD, set the `SENTRY_AUTH_TOKEN` environment variable in your build system. -```bash {filename:.env.sentry-build-plugin} +```bash {tabTitle:Local Development} {filename:.env.sentry-build-plugin} SENTRY_AUTH_TOKEN=sntrys_eyJ... ``` -```bash {filename:CI/CD Environment} +```bash {tabTitle:CI/CD} +# Set as environment variable in your CI/CD system SENTRY_AUTH_TOKEN=sntrys_eyJ... ``` diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx index 3277c643f52622..1c73fa8a72736f 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx @@ -101,9 +101,6 @@ export default withSentryConfig(nextConfig, { // 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, }); ``` @@ -231,12 +228,19 @@ The example above samples 100% of traces in development and 10% in production. M ### 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. +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 and exports `onRequestError` to capture server-side errors. + + + The `onRequestError` hook requires `@sentry/nextjs` version `8.28.0` or higher + and Next.js 15. + ```typescript {filename:instrumentation.ts} +import * as Sentry from "@sentry/nextjs"; + export async function register() { if (process.env.NEXT_RUNTIME === "nodejs") { await import("./sentry.server.config"); @@ -246,6 +250,9 @@ export async function register() { await import("./sentry.edge.config"); } } + +// Capture errors from Server Components, middleware, and proxies +export const onRequestError = Sentry.captureRequestError; ``` @@ -269,25 +276,6 @@ Create `app/global-error.tsx` to capture errors that occur anywhere in your App -### Capture Server Component Errors - -To capture errors from nested React Server Components, export the `onRequestError` hook from your `instrumentation.ts` file. - - - Requires `@sentry/nextjs` version `8.28.0` or higher and Next.js 15. - - - - - - - - - - - - - ### Server Actions Wrap your Server Actions with `Sentry.withServerActionInstrumentation()`. @@ -362,19 +350,11 @@ Prevent ad blockers from blocking Sentry events by routing them through your Nex This increases server load. Consider the trade-off for your application. - - -If you're using Next.js middleware (`middleware.ts`) or proxy (`proxy.ts`) that intercepts requests, you may need to exclude the tunnel route. When using `tunnelRoute: true`, Sentry generates a random route on each build. For compatibility, use a fixed route instead: - -```typescript {filename:next.config.ts} -export default withSentryConfig(nextConfig, { - tunnelRoute: "/monitoring", -}); -``` + -Then exclude it in your middleware or proxy: +If you're using Next.js middleware (`middleware.ts`) that intercepts requests, exclude the tunnel route: -```typescript {filename:middleware.ts or proxy.ts} +```typescript {filename:middleware.ts} export const config = { matcher: ["/((?!monitoring|_next/static|_next/image|favicon.ico).*)"], }; @@ -387,11 +367,8 @@ export const config = { ```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", + // Use a fixed route (recommended) + tunnelRoute: "/monitoring", }); ``` @@ -515,16 +492,6 @@ Sentry.init({ // Capture 100% in dev, 10% in production tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, - - // Or use sampling function for more control - tracesSampler: ({ name, parentSampled }) => { - // Always trace errors - if (name.includes("error")) return 1.0; - // Use parent's sampling decision if available - if (parentSampled !== undefined) return parentSampled; - // Default sample rate - return 0.1; - }, }); ``` diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx index 4143806f32bbc9..d0259e554d886a 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx @@ -60,10 +60,16 @@ pnpm add @sentry/nextjs Extend your app's default Next.js options by adding `withSentryConfig` into your `next.config.ts` (or `next.config.js`) file. + + Pages Router applications typically use Webpack. The Webpack configuration + includes auto-instrumentation options that automatically wrap your API routes + and page data fetching methods. + + -```typescript {filename:next.config.ts} +```typescript {tabTitle:Webpack} {filename:next.config.ts} import type { NextConfig } from "next"; import { withSentryConfig } from "@sentry/nextjs"; @@ -78,8 +84,36 @@ export default withSentryConfig(nextConfig, { // 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, + // Webpack-specific options for Pages Router + webpack: { + // Auto-instrument API routes and data fetching methods (default: true) + autoInstrumentServerFunctions: true, + + // Auto-instrument middleware (default: true) + autoInstrumentMiddleware: true, + + // Tree-shake Sentry logger statements to reduce bundle size + treeshake: { + removeDebugLogging: true, + }, + }, +}); +``` + +```typescript {tabTitle:Turbopack} {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, }); ``` @@ -111,6 +145,9 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", + // Adds request headers and IP for users + sendDefaultPii: true, + // Capture 100% in dev, 10% in production // Adjust based on your traffic volume tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, @@ -130,6 +167,9 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", + // Adds request headers and IP for users + sendDefaultPii: true, + // Capture 100% in dev, 10% in production // Adjust based on your traffic volume tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, @@ -142,6 +182,9 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "___PUBLIC_DSN___", + // Adds request headers and IP for users + sendDefaultPii: true, + // Capture 100% in dev, 10% in production // Adjust based on your traffic volume tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx index 942e36dd34a57a..35a83ad0555a2d 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx @@ -6,6 +6,8 @@ description: "Additional configuration for Next.js applications using Webpack in 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. +For a complete reference of all build configuration options, see the [Build Configuration](/platforms/javascript/guides/nextjs/configuration/build/) documentation. + If you're using Next.js 15+ with Turbopack (the default), you don't need this guide. See the [main manual @@ -14,13 +16,14 @@ This guide covers the configuration differences for **Next.js applications using ## Key Differences: Webpack vs Turbopack -| Feature | Turbopack | Webpack | -| ------------------------------- | ------------------------------- | ---------------------------------------- | -| Server function instrumentation | Automatic via Next.js telemetry | Build-time code injection | -| Middleware instrumentation | Automatic via Next.js telemetry | Build-time code injection | -| Source map upload | Post-build via CLI (default) | During build via plugin (default) | -| Route exclusion | Not supported | Supported via `excludeServerRoutes` | -| React component annotation | Not supported | Supported via `reactComponentAnnotation` | +| 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-compile during build | During build via plugin (default) | +| Route exclusion | Not supported | Supported via `webpack.excludeServerRoutes` | +| React component annotation | Not supported | Supported via `webpack.reactComponentAnnotation` | +| Logger tree-shaking | Not supported | Supported via `webpack.treeshake.removeDebugLogging` | ## Auto-Instrumentation Options @@ -44,14 +47,21 @@ These options are enabled by default with Webpack. Disable them if you prefer ma import { withSentryConfig } from "@sentry/nextjs"; export default withSentryConfig(nextConfig, { - // Instrument Pages Router API routes and data fetching methods (default: true) - autoInstrumentServerFunctions: true, + webpack: { + // Instrument Pages Router API routes and data fetching methods (default: true) + autoInstrumentServerFunctions: true, + + // Instrument Next.js middleware (default: true) + autoInstrumentMiddleware: true, - // Instrument Next.js middleware (default: true) - autoInstrumentMiddleware: true, + // Instrument App Router components (default: true) + autoInstrumentAppDirectory: true, - // Instrument App Router components (default: true) - autoInstrumentAppDirectory: true, + // Tree-shake Sentry logger statements to reduce bundle size + treeshake: { + removeDebugLogging: true, + }, + }, }); ``` @@ -85,11 +95,13 @@ Specify routes as URL paths (not file system paths). Routes must have a leading import { withSentryConfig } from "@sentry/nextjs"; export default withSentryConfig(nextConfig, { - excludeServerRoutes: [ - "/api/health", - "/api/excluded/[parameter]", - /^\/internal\//, // Regex for all /internal/* routes - ], + webpack: { + excludeServerRoutes: [ + "/api/health", + "/api/excluded/[parameter]", + /^\/internal\//, // Regex for all /internal/* routes + ], + }, }); ``` @@ -123,9 +135,6 @@ export default withSentryConfig(nextConfig, { org: "___ORG_SLUG___", project: "___PROJECT_SLUG___", authToken: process.env.SENTRY_AUTH_TOKEN, - - // Default webpack behavior - uploads during build - useRunAfterProductionCompileHook: false, }); ``` @@ -277,8 +286,10 @@ Enable `reactComponentAnnotation` to track component names in your application. import { withSentryConfig } from "@sentry/nextjs"; export default withSentryConfig(nextConfig, { - reactComponentAnnotation: { - enabled: true, + webpack: { + reactComponentAnnotation: { + enabled: true, + }, }, }); ``` @@ -304,9 +315,11 @@ If you have components you don't want annotated (for privacy or performance reas import { withSentryConfig } from "@sentry/nextjs"; export default withSentryConfig(nextConfig, { - reactComponentAnnotation: { - enabled: true, - ignoredComponents: ["SensitiveForm", "InternalDebugPanel"], + webpack: { + reactComponentAnnotation: { + enabled: true, + ignoredComponents: ["SensitiveForm", "InternalDebugPanel"], + }, }, }); ``` diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 8b90bce2795628..18bc0cf53aad6a 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -271,54 +271,56 @@ export function VerificationChecklist({ const toggleItem = useCallback( (itemId: string, itemLabel: string) => { - setCheckedItems(prev => { - const newChecked = !prev[itemId]; - const newState = {...prev, [itemId]: newChecked}; + // Get current state to calculate new values + const currentChecked = checkedItems[itemId] ?? false; + const newChecked = !currentChecked; - // Emit event for checking/unchecking item - emit('Checklist Item Toggle', { - props: { - checked: newChecked, - checklistId, - itemId, - itemLabel, - page: window.location.pathname, - }, - }); + // Update checked state (pure state update) + setCheckedItems(prev => ({...prev, [itemId]: newChecked})); + + // Side effects happen after state update, outside the updater + // Emit event for checking/unchecking item + emit('Checklist Item Toggle', { + props: { + checked: newChecked, + checklistId, + itemId, + itemLabel, + page: window.location.pathname, + }, + }); - // If sequential and checking an item, expand the next unchecked item - if (sequential && newChecked) { - const currentIndex = items.findIndex(item => item.id === itemId); - if (currentIndex !== -1 && currentIndex < items.length - 1) { - // Find the next unchecked item - for (let i = currentIndex + 1; i < items.length; i++) { - if (!newState[items[i].id]) { - setExpandedItems(prevExpanded => ({ - ...prevExpanded, - [itemId]: false, // Collapse current - [items[i].id]: true, // Expand next - })); - break; - } + // 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 (!checkedItems[items[i].id]) { + setExpandedItems(prevExpanded => ({ + ...prevExpanded, + [itemId]: false, // Collapse current + [items[i].id]: true, // Expand next + })); + break; } } } + } - // Check if all items are now complete - const newCompletedCount = items.filter(item => newState[item.id]).length; - if (newCompletedCount === items.length && newChecked) { - emit('Checklist Complete', { - props: { - checklistId, - page: window.location.pathname, - }, - }); - } - - return newState; - }); + // Check if all items are now complete + const newCompletedCount = + items.filter(item => (item.id === itemId ? newChecked : checkedItems[item.id])).length; + if (newCompletedCount === items.length && newChecked) { + emit('Checklist Complete', { + props: { + checklistId, + page: window.location.pathname, + }, + }); + } }, - [checklistId, emit, items, sequential] + [checkedItems, checklistId, emit, items, sequential] ); const toggleExpanded = useCallback((itemId: string) => { From d6dffe76370d12514d3d3d7119200b1b9068b6b1 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:23:29 +0000 Subject: [PATCH 19/27] [getsentry/action-github-commit] Auto commit --- src/components/verificationChecklist/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx index 18bc0cf53aad6a..ad5b4cd7cb6102 100644 --- a/src/components/verificationChecklist/index.tsx +++ b/src/components/verificationChecklist/index.tsx @@ -309,8 +309,9 @@ export function VerificationChecklist({ } // Check if all items are now complete - const newCompletedCount = - items.filter(item => (item.id === itemId ? newChecked : checkedItems[item.id])).length; + const newCompletedCount = items.filter(item => + item.id === itemId ? newChecked : checkedItems[item.id] + ).length; if (newCompletedCount === items.length && newChecked) { emit('Checklist Complete', { props: { From f02564be800f0c8d667399f4b5e1a6fd74229609 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Tue, 16 Dec 2025 14:33:29 -0500 Subject: [PATCH 20/27] getting started with nextjs --- src/components/sidebar/platformSidebar.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/sidebar/platformSidebar.tsx b/src/components/sidebar/platformSidebar.tsx index 2867f4ef6ef07d..fb964dbf643314 100644 --- a/src/components/sidebar/platformSidebar.tsx +++ b/src/components/sidebar/platformSidebar.tsx @@ -47,12 +47,18 @@ export function PlatformSidebar({ ? `platforms/${platformName}/guides/${guideName}` : `platforms/${platformName}`; + // Use "Getting Started" for Next.js, default title for other platforms + const isNextJs = platformName === 'javascript' && guideName === 'nextjs'; + const sidebarTitle = isNextJs + ? 'Getting Started' + : `Sentry for ${(guide || platform).title}`; + return (
From 70fab535ab4323032d99aec1020030d59c32a1c6 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Tue, 16 Dec 2025 15:05:50 -0500 Subject: [PATCH 21/27] remove checklist --- .../common/troubleshooting/index.mdx | 53 -- .../verificationChecklist/index.tsx | 515 ------------------ .../verificationChecklist/style.module.scss | 418 -------------- src/mdxComponents.ts | 3 - 4 files changed, 989 deletions(-) delete mode 100644 src/components/verificationChecklist/index.tsx delete mode 100644 src/components/verificationChecklist/style.module.scss diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index 8967d300652a84..f32c8bda4e3807 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -38,59 +38,6 @@ If you set up the Sentry SDK and it's not sending any data to Sentry: - - - - -Use this checklist to systematically verify your Next.js Sentry setup: - - - - - - - - - - - - - - - - If you update your Sentry SDK to a new major version, you might encounter breaking changes that need some adaption on your end. diff --git a/src/components/verificationChecklist/index.tsx b/src/components/verificationChecklist/index.tsx deleted file mode 100644 index ad5b4cd7cb6102..00000000000000 --- a/src/components/verificationChecklist/index.tsx +++ /dev/null @@ -1,515 +0,0 @@ -'use client'; - -import { - Children, - isValidElement, - ReactElement, - ReactNode, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import {ArrowRightIcon, CheckIcon, ChevronDownIcon} from '@radix-ui/react-icons'; - -import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent'; - -import styles from './style.module.scss'; - -type ChecklistItemProps = { - id: string; - label: string; - children?: ReactNode; - description?: string; - /** Secondary link (usually to docs) */ - docsLink?: string; - docsLinkText?: string; - /** Primary link (usually to Sentry UI) */ - link?: string; - linkText?: string; - /** Onboarding option ID - shows "(Optional)" when this option is unchecked */ - optionId?: string; -}; - -// Marker symbol to identify ChecklistItem components -const CHECKLIST_ITEM_MARKER = Symbol.for('sentry-docs-checklist-item'); - -// Individual checklist item component for use as children -// Note: This component doesn't render anything itself - it's used as a container -// for props/children that VerificationChecklist extracts and renders -function ChecklistItemComponent(_props: ChecklistItemProps) { - // Return null - VerificationChecklist extracts props and renders items itself - return null; -} -ChecklistItemComponent.displayName = 'ChecklistItem'; -// Static marker for identification -( - ChecklistItemComponent as unknown as {__checklistItemMarker: symbol} -).__checklistItemMarker = CHECKLIST_ITEM_MARKER; - -export const ChecklistItem = ChecklistItemComponent; - -type Props = { - /** Unique identifier for this checklist (used for localStorage) */ - checklistId?: string; - children?: ReactNode; - /** Enable collapsible items (default: true) */ - collapsible?: boolean; - /** Auto-expand next item when current is checked (default: true) */ - sequential?: boolean; -}; - -function getStorageKey(checklistId: string): string { - return `sentry-docs-checklist-${checklistId}`; -} - -export function VerificationChecklist({ - children, - checklistId = 'default', - collapsible = true, - sequential = true, -}: Props) { - const [checkedItems, setCheckedItems] = useState>({}); - const [expandedItems, setExpandedItems] = useState>({}); - const [mounted, setMounted] = useState(false); - const [optionalItemIds, setOptionalItemIds] = useState>(new Set()); - const listRef = useRef(null); - const {emit} = usePlausibleEvent(); - - // Helper to check if an element is a ChecklistItem - // MDX may transform the component type, so we check multiple ways - const isChecklistItemElement = useCallback((child: ReactElement): boolean => { - const type = child.type; - const props = child.props as Record; - - // Check for our static marker (most reliable) - if (typeof type === 'function') { - const funcType = type as { - __checklistItemMarker?: symbol; - displayName?: string; - name?: string; - }; - if (funcType.__checklistItemMarker === CHECKLIST_ITEM_MARKER) { - return true; - } - if (funcType.displayName === 'ChecklistItem' || funcType.name === 'ChecklistItem') { - return true; - } - } - - // Direct reference check - if (type === ChecklistItem) { - return true; - } - - // MDX might set mdxType or originalType - if (props.mdxType === 'ChecklistItem' || props.originalType === ChecklistItem) { - return true; - } - - // Last resort: check if props match ChecklistItem structure and it's not a DOM element - if ( - typeof type !== 'string' && - typeof props.id === 'string' && - typeof props.label === 'string' - ) { - return true; - } - - return false; - }, []); - - // Extract items from children - memoize to avoid infinite loops - // MDX may wrap children in fragments, so we need to deeply traverse - const items = useMemo(() => { - const extracted: ChecklistItemProps[] = []; - - const processChild = (child: ReactNode) => { - if (!isValidElement(child)) { - return; - } - - // Check if this is a ChecklistItem - if (isChecklistItemElement(child)) { - const props = child.props as ChecklistItemProps; - if (props.id && props.label) { - extracted.push(props); - } - return; - } - - // If it's a fragment or wrapper, recurse into its children - const childProps = child.props as {children?: ReactNode}; - if (childProps.children) { - Children.forEach(childProps.children, processChild); - } - }; - - Children.forEach(children, processChild); - return extracted; - }, [children, isChecklistItemElement]); - - // Get the first item ID for initial expanded state - const firstItemId = items.length > 0 ? items[0].id : null; - - // Load checked items from localStorage on mount and set initial expanded state - useEffect(() => { - setMounted(true); - try { - const stored = localStorage.getItem(getStorageKey(checklistId)); - if (stored) { - setCheckedItems(JSON.parse(stored)); - } - } catch { - // Ignore localStorage errors - } - - // If collapsible, expand the first item by default - if (collapsible && firstItemId) { - setExpandedItems({[firstItemId]: true}); - } - }, [checklistId, collapsible, firstItemId]); - - // Save to localStorage when checked items change - useEffect(() => { - if (!mounted) { - return; - } - try { - localStorage.setItem(getStorageKey(checklistId), JSON.stringify(checkedItems)); - } catch { - // Ignore localStorage errors - } - }, [checkedItems, checklistId, mounted]); - - // Watch for which onboarding options are enabled/disabled - // We watch the OnboardingOptionButtons checkboxes to determine which options are enabled - useEffect(() => { - if (!mounted) { - return undefined; - } - - const updateOptionalItems = () => { - const newOptionalIds = new Set(); - const onboardingContainer = document.querySelector('.onboarding-options'); - - items.forEach(item => { - if (item.optionId && onboardingContainer) { - // Find all Radix checkboxes in the onboarding options - // The checkbox has data-state="checked" or data-state="unchecked" - const checkboxes = onboardingContainer.querySelectorAll( - 'button[role="checkbox"]' - ); - - // Map option IDs to their display names - const optionNameMap: Record = { - performance: 'Tracing', - 'session-replay': 'Session Replay', - logs: 'Logs', - 'error-monitoring': 'Error Monitoring', - }; - - const optionName = optionNameMap[item.optionId]; - if (!optionName) { - return; - } - - // Find the checkbox whose parent label contains the option name - let isChecked = false; - checkboxes.forEach(checkbox => { - const label = checkbox.closest('label'); - if (label && label.textContent?.includes(optionName)) { - isChecked = checkbox.getAttribute('data-state') === 'checked'; - } - }); - - if (!isChecked) { - newOptionalIds.add(item.id); - } - } - }); - setOptionalItemIds(newOptionalIds); - }; - - // Initial check after a short delay to let onboarding buttons render - const initialTimeout = setTimeout(updateOptionalItems, 100); - - // Set up MutationObserver to watch for checkbox state changes - const observer = new MutationObserver(() => { - updateOptionalItems(); - }); - - // Watch the document for data-state attribute changes on checkboxes - observer.observe(document.body, { - attributes: true, - attributeFilter: ['data-state'], - subtree: true, - }); - - // Also listen for click events on the onboarding buttons container - const handleClick = (e: Event) => { - const target = e.target as HTMLElement; - if (target.closest('.onboarding-options')) { - // Delay to let checkbox state update - setTimeout(updateOptionalItems, 50); - } - }; - document.addEventListener('click', handleClick); - - return () => { - clearTimeout(initialTimeout); - observer.disconnect(); - document.removeEventListener('click', handleClick); - }; - }, [items, mounted]); - - // All items are always visible, but some are marked as optional - const completedCount = items.filter(item => checkedItems[item.id]).length; - const totalCount = items.length; - const allComplete = completedCount === totalCount && totalCount > 0; - - const toggleItem = useCallback( - (itemId: string, itemLabel: string) => { - // Get current state to calculate new values - const currentChecked = checkedItems[itemId] ?? false; - const newChecked = !currentChecked; - - // Update checked state (pure state update) - setCheckedItems(prev => ({...prev, [itemId]: newChecked})); - - // Side effects happen after state update, outside the updater - // Emit event for checking/unchecking item - emit('Checklist Item Toggle', { - props: { - checked: newChecked, - checklistId, - itemId, - itemLabel, - page: window.location.pathname, - }, - }); - - // If sequential and checking an item, expand the next unchecked item - if (sequential && newChecked) { - const currentIndex = items.findIndex(item => item.id === itemId); - if (currentIndex !== -1 && currentIndex < items.length - 1) { - // Find the next unchecked item - for (let i = currentIndex + 1; i < items.length; i++) { - if (!checkedItems[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 => - item.id === itemId ? newChecked : checkedItems[item.id] - ).length; - if (newCompletedCount === items.length && newChecked) { - emit('Checklist Complete', { - props: { - checklistId, - page: window.location.pathname, - }, - }); - } - }, - [checkedItems, checklistId, emit, items, sequential] - ); - - const toggleExpanded = useCallback((itemId: string) => { - setExpandedItems(prev => ({...prev, [itemId]: !prev[itemId]})); - }, []); - - const handleLinkClick = useCallback( - (itemId: string, linkText: string, link: string) => { - emit('Checklist Link Click', { - props: { - checklistId, - itemId, - link, - linkText, - page: window.location.pathname, - }, - }); - }, - [checklistId, emit] - ); - - // Get children content for each item - const getItemChildren = (itemId: string): ReactNode => { - let itemChildren: ReactNode = null; - - const processChild = (child: ReactNode) => { - if (!isValidElement(child) || itemChildren !== null) { - return; - } - - if (isChecklistItemElement(child)) { - const props = child.props as ChecklistItemProps; - if (props.id === itemId) { - itemChildren = props.children; - } - return; - } - - // If it's a fragment or wrapper, recurse into its children - const childProps = child.props as {children?: ReactNode}; - if (childProps.children) { - Children.forEach(childProps.children, processChild); - } - }; - - Children.forEach(children, processChild); - return itemChildren; - }; - - return ( -
-
-
-
0 ? (completedCount / totalCount) * 100 : 0}%`, - }} - /> -
- - {completedCount} of {totalCount} complete - -
- -
    - {items.map(item => { - const isChecked = checkedItems[item.id] || false; - const isExpanded = collapsible ? expandedItems[item.id] || false : true; - const itemChildren = getItemChildren(item.id); - const hasContent = !!itemChildren || !!item.description || !!item.link; - const isOptional = optionalItemIds.has(item.id); - - return ( -
  • -
    toggleExpanded(item.id) : undefined} - style={collapsible ? {cursor: 'pointer'} : undefined} - > -
    - - { - e.stopPropagation(); - toggleItem(item.id, item.label); - }} - onClick={e => e.stopPropagation()} - className={styles.checkbox} - /> - { - e.stopPropagation(); - toggleItem(item.id, item.label); - }} - > - {isChecked && } - - - - - {item.label} - {isOptional && ( - (Optional) - )} - - -
    -
    - {item.docsLink && ( - { - e.stopPropagation(); - handleLinkClick( - item.id, - item.docsLinkText || 'Learn more', - item.docsLink! - ); - }} - > - {item.docsLinkText || 'Learn more'} - - )} - {collapsible && ( - - )} -
    -
    - {isExpanded && hasContent && ( -
    - {item.description && ( -

    {item.description}

    - )} - {item.link && ( - - handleLinkClick(item.id, item.linkText || 'Open', item.link!) - } - > - {item.linkText || 'Open'} - - - )} - {itemChildren && ( -
    {itemChildren}
    - )} -
    - )} -
  • - ); - })} -
- - {allComplete && ( -
- - All done! Sentry is successfully configured. -
- )} -
- ); -} diff --git a/src/components/verificationChecklist/style.module.scss b/src/components/verificationChecklist/style.module.scss deleted file mode 100644 index 879bc1082aa571..00000000000000 --- a/src/components/verificationChecklist/style.module.scss +++ /dev/null @@ -1,418 +0,0 @@ -.checklist { - border: 1px solid var(--gray-200); - border-radius: 0.5rem; - padding: 1.25rem; - margin: 1rem 0; - background: var(--gray-50); -} - -.progress { - display: flex; - align-items: center; - gap: 1rem; - margin-bottom: 1rem; -} - -.progressBar { - flex: 1; - height: 8px; - background: var(--gray-200); - border-radius: 4px; - overflow: hidden; -} - -.progressFill { - height: 100%; - background: linear-gradient(90deg, #6c5fc7 0%, #8b7fd9 100%); - border-radius: 4px; - transition: width 0.3s ease; -} - -.progressText { - font-size: 0.875rem; - color: var(--gray-600); - white-space: nowrap; -} - -.items { - list-style: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.item { - border-radius: 0.375rem; - background: var(--white); - border: 1px solid var(--gray-100); - transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease; - overflow: hidden; - - &:hover { - border-color: var(--gray-200); - } - - &.expanded { - border-color: var(--gray-200); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); - } - - &.collapsed { - .itemHeader { - padding: 0.75rem; - } - } -} - -.itemHeader { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - padding: 0.75rem; - user-select: none; - - @media (max-width: 640px) { - flex-wrap: wrap; - gap: 0.5rem; - } -} - -.headerLeft { - display: flex; - align-items: flex-start; - gap: 0.75rem; - flex: 1; - min-width: 0; -} - -.headerRight { - display: flex; - align-items: center; - gap: 0.75rem; - flex-shrink: 0; - - @media (max-width: 640px) { - margin-left: auto; - } -} - -.checkboxWrapper { - position: relative; - flex-shrink: 0; - margin-top: 2px; -} - -.checkbox { - position: absolute; - opacity: 0; - width: 0; - height: 0; -} - -.customCheckbox { - display: flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border: 2px solid var(--gray-300); - border-radius: 4px; - background: var(--white); - transition: all 0.15s ease; - cursor: pointer; - - &:hover { - border-color: #6c5fc7; - } - - &.checked { - background: #6c5fc7; - border-color: #6c5fc7; - } -} - -.checkIcon { - width: 14px; - height: 14px; - color: white; -} - -.labelContainer { - display: flex; - flex-direction: column; - gap: 0.125rem; - min-width: 0; - flex: 1; -} - -.labelText { - font-weight: 500; - color: var(--gray-900); - transition: color 0.15s ease; - display: inline-flex; - align-items: center; - gap: 0.5rem; - flex-wrap: wrap; - - &.checked { - text-decoration: line-through; - color: var(--gray-500); - } -} - -.optionalBadge { - font-size: 0.75rem; - font-weight: 400; - color: var(--gray-400); - background: var(--gray-100); - padding: 0.125rem 0.375rem; - border-radius: 4px; -} - -.docsLink { - font-size: 0.8125rem; - color: var(--gray-500); - text-decoration: none; - white-space: nowrap; - - &:hover { - text-decoration: underline; - color: var(--gray-600); - } -} - -.expandButton { - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 0; - border: 1px solid var(--gray-200); - border-radius: 4px; - background: var(--white); - cursor: pointer; - transition: all 0.15s ease; - - &:hover { - background: var(--gray-50); - border-color: var(--gray-300); - } - - &.expanded { - .chevronIcon { - transform: rotate(180deg); - } - } -} - -.chevronIcon { - width: 16px; - height: 16px; - color: var(--gray-500); - transition: transform 0.2s ease; -} - -.expandedContent { - padding: 0 0.75rem 0.75rem 2.75rem; - animation: slideDown 0.2s ease; - - // Handle MDX content directly in expanded area - > :global(div:first-child), - > :global(pre:first-child) { - margin-top: 0.5rem; - } - - // Paragraphs before code blocks - :global(p + pre), - :global(p + div) { - margin-top: 0.75rem; - } - - // Remove extra margin from last element - > :global(*:last-child) { - margin-bottom: 0; - } - - // Code tabs container - :global([class*="codeContext"]) { - margin-top: 0.5rem; - } -} - -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-8px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.expandedDescription { - font-size: 0.875rem; - color: var(--gray-600); - margin: 0 0 0.75rem 0; - line-height: 1.5; -} - -.primaryLink { - display: inline-flex; - align-items: center; - gap: 0.25rem; - font-size: 0.875rem; - font-weight: 500; - color: #6c5fc7; - text-decoration: none; - margin-bottom: 0.75rem; - - &:hover { - text-decoration: underline; - } -} - -.arrowIcon { - width: 14px; - height: 14px; -} - -.itemContent { - margin-top: 0.75rem; - - // Reset some MDX component styles within content - > :global(p:first-child) { - margin-top: 0; - } - - > :global(*:last-child) { - margin-bottom: 0; - } - - // Code blocks get proper spacing - :global(pre) { - margin: 0.5rem 0; - - &:last-child { - margin-bottom: 0; - } - } -} - -.successMessage { - display: flex; - align-items: center; - gap: 0.5rem; - margin-top: 1rem; - padding: 0.75rem 1rem; - background: #d3f9d8; - border: 1px solid #69db7c; - border-radius: 0.375rem; - color: #2b8a3e; - font-weight: 500; -} - -.successIcon { - width: 18px; - height: 18px; -} - -// Dark mode support -:global(.dark) { - .checklist { - background: #1a1523; - border-color: #3e3446; - } - - .item { - background: #231c3d; - border-color: #3e3446; - - &:hover { - border-color: #584774; - } - - &.expanded { - border-color: #584774; - } - } - - .customCheckbox { - background: #231c3d; - border-color: #584774; - - &:hover { - border-color: #8b7fd9; - } - - &.checked { - background: #8b7fd9; - border-color: #8b7fd9; - } - } - - .labelText { - color: #e2d9e9; - - &.checked { - color: #9481a4; - } - } - - .optionalBadge { - background: #3e3446; - color: #9481a4; - } - - .expandedDescription { - color: #bbadc6; - } - - .progressBar { - background: #3e3446; - } - - .progressText { - color: #bbadc6; - } - - .primaryLink { - color: #a796f0; - - &:hover { - color: #c4baff; - } - } - - .docsLink { - color: #9481a4; - - &:hover { - color: #bbadc6; - } - } - - .expandButton { - background: #231c3d; - border-color: #3e3446; - - &:hover { - background: #2d2447; - border-color: #584774; - } - } - - .chevronIcon { - color: #9481a4; - } - - .successMessage { - background: rgba(45, 106, 79, 0.3); - border-color: #40916c; - color: #69db7c; - } -} diff --git a/src/mdxComponents.ts b/src/mdxComponents.ts index e09e52ef3a5216..c6458a492276c6 100644 --- a/src/mdxComponents.ts +++ b/src/mdxComponents.ts @@ -55,7 +55,6 @@ import { } from './components/splitLayout'; import {StepComponent, StepConnector} from './components/stepConnector'; import {TableOfContents} from './components/tableOfContents'; -import {ChecklistItem, VerificationChecklist} from './components/verificationChecklist'; import {VersionRequirement} from './components/version-requirement'; import {VimeoEmbed} from './components/video'; @@ -117,8 +116,6 @@ export function mdxComponents( SplitSectionCode, StepComponent, StepConnector, - VerificationChecklist, - ChecklistItem, VimeoEmbed, VersionRequirement, a: SmartLink, From 62ffddf4d2232f3ffb91b89d9378d469dc2ac79a Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Tue, 16 Dec 2025 18:21:56 -0500 Subject: [PATCH 22/27] Update docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx Co-authored-by: Alex Krawiec --- .../javascript/guides/nextjs/manual-setup/webpack-setup.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx index 35a83ad0555a2d..4df7d316ffa0be 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx @@ -70,7 +70,7 @@ export default withSentryConfig(nextConfig, { -## Exclude Routes from Instrumentation +## 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. From 35e8350ea79c65674030fee30418d179f04450a9 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Wed, 17 Dec 2025 12:57:46 -0500 Subject: [PATCH 23/27] updates --- .../javascript/guides/nextjs/index.mdx | 6 +- .../guides/nextjs/manual-setup/index.mdx | 136 +++++++-------- .../nextjs/manual-setup/pages-router.mdx | 156 ++++++++++++++---- .../nextjs/manual-setup/webpack-setup.mdx | 33 ++-- src/components/featureBadge/index.tsx | 17 ++ src/components/featureBadge/style.module.scss | 38 +++++ src/components/stepConnector/index.tsx | 13 +- .../stepConnector/style.module.scss | 16 ++ src/mdxComponents.ts | 2 + 9 files changed, 285 insertions(+), 132 deletions(-) create mode 100644 src/components/featureBadge/index.tsx create mode 100644 src/components/featureBadge/style.module.scss diff --git a/docs/platforms/javascript/guides/nextjs/index.mdx b/docs/platforms/javascript/guides/nextjs/index.mdx index 78025f14c442bd..b36cdc3411c5fa 100644 --- a/docs/platforms/javascript/guides/nextjs/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/index.mdx @@ -171,7 +171,7 @@ export default withSentryConfig(nextConfig, { authToken: process.env.SENTRY_AUTH_TOKEN, // Route Sentry requests through your server (avoids ad-blockers) - tunnelRoute: "/monitoring-tunnel", + tunnelRoute: "/monitoring", silent: !process.env.CI, }); @@ -282,7 +282,7 @@ npm run dev 3. Click **"Throw Sample Error"** -4. Check your data in Sentry: +### Check Your Data in Sentry **Errors** — [Open Issues](https://sentry.io/orgredirect/organizations/:orgslug/issues/) @@ -306,7 +306,7 @@ Watch a video-like recording of your session, including the moment the error occ -**Logs** — [Open Logs](https://sentry.io/orgredirect/organizations/:orgslug/explore/logs/) +**Logs** — [Open Logs](https://sentry.io/orgredirect/organizations/:orgslug/explore/logs/) See structured log entries from your application. You can send logs from anywhere: diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx index 1c73fa8a72736f..8ffd367b7f41cb 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/index.mdx @@ -1,7 +1,7 @@ --- title: "Manual Setup" sidebar_order: 1 -description: "Learn how to set up and configure Sentry in your Next.js application, capture your first errors, logs and traces and view them in Sentry" +description: "Learn how to set up and configure Sentry in your Next.js application, capture your first errors, logs and traces and view them in Sentry." sdk: sentry.javascript.nextjs categories: - javascript @@ -38,7 +38,7 @@ Choose the features you want to configure: 1. **Install** - Add the Sentry SDK to your project 2. **Configure** - Set up SDK initialization files and Next.js configuration -3. **Verify each feature** - For each feature you enabled, we'll show you how to test it and verify it's working in Sentry +3. **Verify** - Test error monitoring and any additional features you enabled @@ -415,22 +415,24 @@ Add this button to any page and click it to trigger a test error. Open [**Issues**](https://sentry.io/orgredirect/organizations/:orgslug/issues/) in Sentry to see your test error. [Learn more about capturing errors](/platforms/javascript/guides/nextjs/usage/). - +## Verify Additional Features -## Session Replay +Based on the features you selected above, verify each one is working correctly. -Session Replay is already configured in your `instrumentation-client.ts` file. The `replayIntegration()` captures video-like reproductions of user sessions. + -### Configuration +### Session Replay + +Session Replay captures video-like reproductions of user sessions. It's configured with `replayIntegration()` in your client config. -Adjust sample rates in `Sentry.init()`. By default, Sentry masks all DOM text content, images, and user input, giving you heightened confidence that no sensitive data will leave the browser. +By default, Sentry masks all text, inputs, and media. You can customize this in [Privacy Configuration](/platforms/javascript/guides/nextjs/session-replay/privacy/). -You can customize masking behavior to unmask specific elements or mask additional sensitive content. See [Privacy Configuration](/platforms/javascript/guides/nextjs/session-replay/privacy/) for details. +**Verify:** Trigger an error or navigate your app, then check [**Replays**](https://sentry.io/orgredirect/organizations/:orgslug/replays/) in Sentry. @@ -441,19 +443,14 @@ Sentry.init({ integrations: [ Sentry.replayIntegration({ - // Privacy settings (defaults shown) maskAllText: true, maskAllInputs: true, blockAllMedia: true, - - // Unmask specific elements - unmask: [".public-content"], }), ], - // Sample rates - replaysSessionSampleRate: 0.1, // Capture 10% of all sessions - replaysOnErrorSampleRate: 1.0, // Capture 100% of error sessions + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, }); ``` @@ -462,48 +459,22 @@ Sentry.init({ -### Verify - -Trigger an error or navigate around your app, then open [**Replays**](https://sentry.io/orgredirect/organizations/:orgslug/replays/) in Sentry to watch the session recording. [Learn more about Session Replay](/platforms/javascript/guides/nextjs/session-replay/). - -## Tracing - -Tracing is already configured in your SDK initialization files with `tracesSampleRate`. Your Next.js routes and API calls are automatically instrumented. - -### Configuration +### Tracing -Adjust sample rates for production. We recommend starting with a lower rate and increasing as needed. +Tracing is configured with `tracesSampleRate` in your SDK init files. Next.js routes and API calls are automatically instrumented. - - +Add [custom spans](/platforms/javascript/guides/nextjs/tracing/instrumentation/custom-instrumentation/) to trace specific operations in your code. -```typescript {filename:sentry.server.config.ts} -Sentry.init({ - dsn: "___PUBLIC_DSN___", - - // Capture 100% in dev, 10% in production - tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, -}); -``` - - - - - - - -### Custom Spans - -Add custom spans to trace specific operations in your code. See [Custom Instrumentation](/platforms/javascript/guides/nextjs/tracing/instrumentation/custom-instrumentation/) for more examples. +**Verify:** Navigate to any page, then check [**Traces**](https://sentry.io/orgredirect/organizations/:orgslug/explore/traces/) in Sentry. @@ -511,21 +482,13 @@ Add custom spans to trace specific operations in your code. See [Custom Instrume ```typescript import * as Sentry from "@sentry/nextjs"; -// Wrap a function with a span +// Wrap operations with spans const result = await Sentry.startSpan( { name: "expensive-operation", op: "function" }, async () => { - // Your code here return await fetchDataFromAPI(); } ); - -// Or create nested spans -Sentry.startSpan({ name: "parent-operation" }, () => { - Sentry.startSpan({ name: "child-operation" }, () => { - // Nested work - }); -}); ``` @@ -533,26 +496,22 @@ Sentry.startSpan({ name: "parent-operation" }, () => { -### Verify - -Navigate to any page in your app, then open [**Traces**](https://sentry.io/orgredirect/organizations/:orgslug/explore/traces/) in Sentry to see the performance data. [Learn more about Tracing](/platforms/javascript/guides/nextjs/tracing/). - -## Logs - -Logs are enabled with `enableLogs: true` in your SDK configuration. Use the Sentry logger to send structured logs. - -### Send a Test Log +### Logs -Use the Sentry logger to send logs from anywhere in your application. +Logs are enabled with `enableLogs: true` in your SDK config. Use the Sentry logger to send structured logs from anywhere in your application. + +Connect popular logging libraries via [Integrations](/platforms/javascript/guides/nextjs/logs/#integrations). + +**Verify:** Add a log statement, trigger it, then check [**Logs**](https://sentry.io/orgredirect/organizations/:orgslug/explore/logs/) in Sentry. @@ -560,17 +519,13 @@ Use the Sentry logger to send logs from anywhere in your application. ```typescript import * as Sentry from "@sentry/nextjs"; -// Simple log message Sentry.logger.info("User clicked checkout button"); -// Log with parameters Sentry.logger.info("Order completed", { orderId: "12345", total: 99.99, }); -// Different log levels -Sentry.logger.debug("Debug information"); Sentry.logger.warn("Warning message"); Sentry.logger.error("Error occurred"); ``` @@ -580,27 +535,46 @@ Sentry.logger.error("Error occurred"); -### Verify - -Add a log statement to your code and trigger it, then open [**Logs**](https://sentry.io/orgredirect/organizations/:orgslug/explore/logs/) in Sentry. [Learn more about Logs](/platforms/javascript/guides/nextjs/logs/) or see available [Integrations](/platforms/javascript/guides/nextjs/logs/#integrations) to connect popular logging libraries. - -## User Feedback + -User Feedback is configured with `feedbackIntegration()` in your `instrumentation-client.ts`. A feedback widget is automatically added to your application. + + -### Verify +### User Feedback -Look for the feedback button in your app (usually bottom-right corner) and submit a test feedback. Open [**User Feedback**](https://sentry.io/orgredirect/organizations/:orgslug/feedback/) in Sentry to see the submission. [Learn more about User Feedback](/platforms/javascript/guides/nextjs/user-feedback/). +User Feedback adds an embeddable widget via `feedbackIntegration()` that lets users report bugs directly from your app. - +**Verify:** Look for the feedback button (bottom-right corner), submit test feedback, then check [**User Feedback**](https://sentry.io/orgredirect/organizations/:orgslug/feedback/) in Sentry. - +[Learn more about User Feedback](/platforms/javascript/guides/nextjs/user-feedback/) -## Hybrid Apps (App Router + Pages Router) + + + +```typescript {filename:instrumentation-client.ts} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + integrations: [ + Sentry.feedbackIntegration({ + colorScheme: "system", + }), + ], +}); +``` + + + + + + + + +

Hybrid Apps (App Router + Pages Router)

If your application uses both the App Router and Pages Router: @@ -613,7 +587,7 @@ If your application uses both the App Router and Pages Router: the appropriate instrumentation. -## Next Steps +

Next Steps

You've successfully integrated Sentry into your Next.js application! Here's what to explore next: @@ -629,3 +603,5 @@ You've successfully integrated Sentry into your Next.js application! Here's what - [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 index d0259e554d886a..093126bef27234 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/pages-router.mdx @@ -16,6 +16,17 @@ This guide covers manual setup for **Next.js applications using the Pages Router +Choose the features you want to configure: + + + ## Install @@ -147,17 +158,29 @@ Sentry.init({ // Adds request headers and IP for users sendDefaultPii: true, + // ___PRODUCT_OPTION_START___ performance // Capture 100% in dev, 10% in production // Adjust based on your traffic volume tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, + // ___PRODUCT_OPTION_END___ performance + integrations: [ + // ___PRODUCT_OPTION_START___ session-replay + Sentry.replayIntegration(), + // ___PRODUCT_OPTION_END___ session-replay + ], + // ___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 - integrations: [Sentry.replayIntegration()], + // Enable logs to be sent to Sentry + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs }); ``` @@ -169,10 +192,17 @@ Sentry.init({ // Adds request headers and IP for users sendDefaultPii: true, + // ___PRODUCT_OPTION_START___ performance // Capture 100% in dev, 10% in production // Adjust based on your traffic volume tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ logs + + // Enable logs to be sent to Sentry + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs }); ``` @@ -184,10 +214,17 @@ Sentry.init({ // Adds request headers and IP for users sendDefaultPii: true, + // ___PRODUCT_OPTION_START___ performance // Capture 100% in dev, 10% in production // Adjust based on your traffic volume tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, + // ___PRODUCT_OPTION_END___ performance + // ___PRODUCT_OPTION_START___ logs + + // Enable logs to be sent to Sentry + enableLogs: true, + // ___PRODUCT_OPTION_END___ logs }); ``` @@ -293,43 +330,97 @@ export default function Home() { Open [**Issues**](https://sentry.io/orgredirect/organizations/:orgslug/issues/) in Sentry to see your test error. [Learn more about capturing errors](/platforms/javascript/guides/nextjs/usage/). -## Manually Capture Exceptions +## Verify Additional Features - - When using Webpack, Pages Router API routes can be auto-instrumented with the - `autoInstrumentServerFunctions` option. See [Webpack - Setup](/platforms/javascript/guides/nextjs/manual-setup/webpack-setup/) for - details. - +Based on the features you selected above, verify each one is working correctly. + + -### Pages Router API Routes +### Session Replay + +Session Replay captures video-like reproductions of user sessions. It's configured with `replayIntegration()` in your client config. -For manual error capturing in API routes, use `Sentry.captureException()`. +**Verify:** Trigger an error or navigate your app, then check [**Replays**](https://sentry.io/orgredirect/organizations/:orgslug/replays/) in Sentry. -```typescript {filename:pages/api/example.ts} +```typescript {filename:sentry.client.config.ts} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + integrations: [Sentry.replayIntegration()], + + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, +}); +``` + + + + + + + + + + + + + + + +### Tracing + +Tracing is configured with `tracesSampleRate` in your SDK init files. Next.js routes and API calls are automatically instrumented. + +**Verify:** Navigate to any page, then check [**Traces**](https://sentry.io/orgredirect/organizations/:orgslug/explore/traces/) in Sentry. + + + + +```typescript {filename:sentry.server.config.ts} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + + tracesSampleRate: process.env.NODE_ENV === "development" ? 1.0 : 0.1, +}); +``` + + + + + + + + + + + + + + + +### Logs + +Logs are enabled with `enableLogs: true` in your SDK config. Use the Sentry logger to send structured logs. + +**Verify:** Add a log statement, trigger it, then check [**Logs**](https://sentry.io/orgredirect/organizations/:orgslug/explore/logs/) in Sentry. + + + + +```typescript 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" }); - } -} + +Sentry.logger.info("User action", { userId: "123" }); +Sentry.logger.warn("Warning message"); +Sentry.logger.error("Error occurred"); ``` @@ -337,6 +428,8 @@ export default async function handler( + + ## Vercel Cron Jobs (Optional) Automatically create [Cron Monitors](/product/crons/) in Sentry for your [Vercel cron jobs](https://vercel.com/docs/cron-jobs). @@ -350,11 +443,6 @@ Automatically create [Cron Monitors](/product/crons/) in Sentry for your [Vercel 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. - - @@ -371,9 +459,7 @@ export default withSentryConfig(nextConfig, { - - -## Hybrid Apps (App Router + Pages Router) +

Hybrid Apps (App Router + Pages Router)

If your application uses both the App Router and Pages Router: @@ -386,7 +472,7 @@ If your application uses both the App Router and Pages Router: the appropriate instrumentation. -## Next Steps +

Next Steps

- [Logs Integrations](/platforms/javascript/guides/nextjs/logs/#integrations) - Connect popular logging libraries like Pino, Winston, and Bunyan - [Distributed Tracing](/platforms/javascript/guides/nextjs/tracing/distributed-tracing/) - Trace requests across services and microservices @@ -400,3 +486,5 @@ If your application uses both the App Router and Pages Router: - [Get support](https://sentry.zendesk.com/hc/en-us/) + + diff --git a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx index 35a83ad0555a2d..16dd365b442470 100644 --- a/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx +++ b/docs/platforms/javascript/guides/nextjs/manual-setup/webpack-setup.mdx @@ -206,11 +206,13 @@ export default withSentryConfig(nextConfig, { project: "___PROJECT_SLUG___", authToken: process.env.SENTRY_AUTH_TOKEN, - // Advanced Webpack plugin options - unstable_sentryWebpackPluginOptions: { - sourcemaps: { - assets: ["./build/**/*.js", "./build/**/*.map"], - ignore: ["node_modules/**"], + webpack: { + // Advanced Webpack plugin options + unstable_sentryWebpackPluginOptions: { + sourcemaps: { + assets: ["./build/**/*.js", "./build/**/*.map"], + ignore: ["node_modules/**"], + }, }, }, }); @@ -340,7 +342,17 @@ Tunneling works identically for both Webpack and Turbopack. Sentry automatically ### Configure Tunnel Route -Enable tunneling to bypass ad-blockers. Sentry automatically handles middleware span filtering for tunnel requests. +Enable tunneling to bypass ad-blockers. Use a fixed route so you can exclude it from middleware if needed. + + + +You can use `tunnelRoute: true` to auto-generate a random route. However, random routes cannot be excluded from middleware matchers since the route changes on each build. This may cause issues if you have middleware that intercepts all requests. + +```typescript +tunnelRoute: true, // Auto-generated random route +``` + + @@ -349,11 +361,8 @@ Enable tunneling to bypass ad-blockers. Sentry automatically handles middleware import { withSentryConfig } from "@sentry/nextjs"; export default withSentryConfig(nextConfig, { - // Auto-generated random route (recommended) - tunnelRoute: true, - - // Or use a fixed route - // tunnelRoute: "/monitoring", + // Use a fixed route (recommended) + tunnelRoute: "/monitoring", }); ``` @@ -374,7 +383,7 @@ If you're upgrading to Turbopack: // Before (Webpack) export default withSentryConfig(nextConfig, { excludeServerRoutes: ["/api/health"], - tunnelRoute: true, + tunnelRoute: "/monitoring", }); // After (Turbopack) diff --git a/src/components/featureBadge/index.tsx b/src/components/featureBadge/index.tsx new file mode 100644 index 00000000000000..5c5a87f2e0afb3 --- /dev/null +++ b/src/components/featureBadge/index.tsx @@ -0,0 +1,17 @@ +import styles from './style.module.scss'; + +type BadgeType = 'new' | 'beta'; +type BadgeSize = 'small' | 'default'; + +interface FeatureBadgeProps { + size?: BadgeSize; + type: BadgeType; +} + +export function FeatureBadge({type, size = 'default'}: FeatureBadgeProps) { + const label = type === 'new' ? 'NEW' : 'BETA'; + const typeClass = type === 'new' ? styles.newBadge : styles.betaBadge; + const sizeClass = size === 'small' ? styles.small : ''; + + return {label}; +} diff --git a/src/components/featureBadge/style.module.scss b/src/components/featureBadge/style.module.scss new file mode 100644 index 00000000000000..46a6f65eb1ee37 --- /dev/null +++ b/src/components/featureBadge/style.module.scss @@ -0,0 +1,38 @@ +// Base badge styles +.newBadge, +.betaBadge { + display: inline-flex; + align-items: center; + font-weight: 600; + line-height: 1; + letter-spacing: 0.05em; + white-space: nowrap; + flex-shrink: 0; + vertical-align: middle; + + // Default size + padding: 0.2rem 0.35rem; + font-size: 0.625rem; + border-radius: 0.2rem; + margin-left: 0.375rem; +} + +// Color variants +.newBadge { + color: rgb(24, 20, 35); + background-color: rgb(0, 242, 97); +} + +.betaBadge { + color: rgb(24, 20, 35); + background-color: rgb(255, 208, 14); +} + +// Small size - compound selector for higher specificity +.newBadge.small, +.betaBadge.small { + padding: 0.1rem 0.2rem; + font-size: 0.5rem; + border-radius: 0.125rem; + margin-left: 0.25rem; +} diff --git a/src/components/stepConnector/index.tsx b/src/components/stepConnector/index.tsx index 795b4430ed45fd..eb9940724602aa 100644 --- a/src/components/stepConnector/index.tsx +++ b/src/components/stepConnector/index.tsx @@ -90,9 +90,16 @@ export function StepComponent({ if (existingToggle) existingToggle.remove(); }); - headings.forEach((h, idx) => { - const stepNumber = startAt + idx; - h.setAttribute('data-step', String(stepNumber)); + let stepNumber = startAt; + headings.forEach(h => { + // Check if heading should be unnumbered (has data-no-number attribute in the MDX) + const noNumber = h.hasAttribute('data-no-number'); + if (noNumber) { + h.setAttribute('data-step', ''); + } else { + h.setAttribute('data-step', String(stepNumber)); + stepNumber++; + } h.classList.add(styles.stepHeading); if (checkable) { diff --git a/src/components/stepConnector/style.module.scss b/src/components/stepConnector/style.module.scss index ab2f05dc171b53..d411ed9f8a9c08 100644 --- a/src/components/stepConnector/style.module.scss +++ b/src/components/stepConnector/style.module.scss @@ -55,6 +55,22 @@ z-index: 2; } +/* Unnumbered steps (data-step="") - same circle style with centered dot */ +.stepHeading[data-step='']::before { + content: ''; +} +.stepHeading[data-step='']::after { + content: ''; + position: absolute; + left: calc(var(--rail-x) - (var(--circle) * 0.25) - var(--pad-left)); + top: calc(0.05em + (var(--circle) * 0.25)); + width: calc(var(--circle) * 0.5); + height: calc(var(--circle) * 0.5); + border-radius: 9999px; + background: var(--gray-a6); + z-index: 2; +} + .stepHeading[data-completed='true']::before { content: '✓'; background: var(--accent-11); diff --git a/src/mdxComponents.ts b/src/mdxComponents.ts index c6458a492276c6..ebc07e7a0dbf9f 100644 --- a/src/mdxComponents.ts +++ b/src/mdxComponents.ts @@ -14,6 +14,7 @@ import {DefinitionList} from './components/definitionList'; import {DevDocsCardGrid} from './components/devDocsCardGrid'; import DocImage from './components/docImage'; import {Expandable} from './components/expandable'; +import {FeatureBadge} from './components/featureBadge'; import {GitHubCodePreview} from './components/githubCodePreview'; import {GitHubDomainChecker} from './components/githubDomainChecker'; import {GradleFeatureConfig} from './components/gradleFeatureConfig'; @@ -86,6 +87,7 @@ export function mdxComponents( ConfigValue, DefinitionList, Expandable, + FeatureBadge, GuideGrid, JsBundleList, LambdaLayerDetail, From f9e61458516fcaea92204a1c782d691823d80474 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:59:06 +0000 Subject: [PATCH 24/27] [getsentry/action-github-commit] Auto commit --- src/components/featureBadge/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/featureBadge/index.tsx b/src/components/featureBadge/index.tsx index 5c5a87f2e0afb3..e75b0967535263 100644 --- a/src/components/featureBadge/index.tsx +++ b/src/components/featureBadge/index.tsx @@ -4,8 +4,8 @@ type BadgeType = 'new' | 'beta'; type BadgeSize = 'small' | 'default'; interface FeatureBadgeProps { - size?: BadgeSize; type: BadgeType; + size?: BadgeSize; } export function FeatureBadge({type, size = 'default'}: FeatureBadgeProps) { From 38ef88805820e14b6bb0deb6cd340cb397ab5df2 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Wed, 17 Dec 2025 14:16:14 -0500 Subject: [PATCH 25/27] sidebar heading color --- src/components/sidebar/style.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/sidebar/style.module.scss b/src/components/sidebar/style.module.scss index b5a96bddbde8e8..3c6815b2782aeb 100644 --- a/src/components/sidebar/style.module.scss +++ b/src/components/sidebar/style.module.scss @@ -202,7 +202,7 @@ :global(.sidebar-section-header) { font-size: 0.75rem; font-weight: 600; - color: #6e47ae; + color: var(--gray-11); text-transform: uppercase; letter-spacing: 0.05em; padding: 0.5rem 0 0.375rem; From ee240f6d54f702c181426713ea979ff87a4d3a26 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Wed, 17 Dec 2025 14:26:09 -0500 Subject: [PATCH 26/27] aria label fix on steps --- src/components/stepConnector/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/stepConnector/index.tsx b/src/components/stepConnector/index.tsx index eb9940724602aa..2903baaa34da2a 100644 --- a/src/components/stepConnector/index.tsx +++ b/src/components/stepConnector/index.tsx @@ -94,9 +94,11 @@ export function StepComponent({ headings.forEach(h => { // Check if heading should be unnumbered (has data-no-number attribute in the MDX) const noNumber = h.hasAttribute('data-no-number'); + let currentStepNumber: number | null = null; if (noNumber) { h.setAttribute('data-step', ''); } else { + currentStepNumber = stepNumber; h.setAttribute('data-step', String(stepNumber)); stepNumber++; } @@ -106,7 +108,11 @@ export function StepComponent({ const btn = document.createElement('button'); btn.type = 'button'; btn.className = styles.stepToggle; - btn.setAttribute('aria-label', `Toggle completion for step ${stepNumber}`); + // Use appropriate aria-label based on whether step is numbered + const ariaLabel = currentStepNumber !== null + ? `Toggle completion for step ${currentStepNumber}` + : `Toggle completion for ${h.textContent?.trim() || 'this section'}`; + btn.setAttribute('aria-label', ariaLabel); btn.setAttribute('aria-pressed', completed.has(h.id) ? 'true' : 'false'); btn.addEventListener('click', () => { setCompleted(prev => { From 3ae35a3ae119646ee9489133edc44a096ecaa940 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:27:08 +0000 Subject: [PATCH 27/27] [getsentry/action-github-commit] Auto commit --- src/components/stepConnector/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/stepConnector/index.tsx b/src/components/stepConnector/index.tsx index 2903baaa34da2a..1358f236611cd4 100644 --- a/src/components/stepConnector/index.tsx +++ b/src/components/stepConnector/index.tsx @@ -109,9 +109,10 @@ export function StepComponent({ btn.type = 'button'; btn.className = styles.stepToggle; // Use appropriate aria-label based on whether step is numbered - const ariaLabel = currentStepNumber !== null - ? `Toggle completion for step ${currentStepNumber}` - : `Toggle completion for ${h.textContent?.trim() || 'this section'}`; + const ariaLabel = + currentStepNumber !== null + ? `Toggle completion for step ${currentStepNumber}` + : `Toggle completion for ${h.textContent?.trim() || 'this section'}`; btn.setAttribute('aria-label', ariaLabel); btn.setAttribute('aria-pressed', completed.has(h.id) ? 'true' : 'false'); btn.addEventListener('click', () => {