diff --git a/static/app/gettingStartedDocs/javascript/tanstackstart-react/onboarding.tsx b/static/app/gettingStartedDocs/javascript/tanstackstart-react/onboarding.tsx index f19ea466af499d..19b3b3cb0ed2a1 100644 --- a/static/app/gettingStartedDocs/javascript/tanstackstart-react/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript/tanstackstart-react/onboarding.tsx @@ -1,3 +1,4 @@ +import {ExternalLink} from 'sentry/components/core/link'; import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {t, tct} from 'sentry/locale'; @@ -52,7 +53,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - "First, extend your app's default TanStack Start configuration by adding [code:wrapVinxiConfigWithSentry] to your [code:app.config.ts] file:", + 'First, initialize Sentry on the client in your [code:src/router.tsx] file:', {code: } ), }, @@ -62,115 +63,69 @@ export const onboarding: OnboardingConfig = { { label: 'TypeScript', language: 'typescript', - filename: 'app.config.ts', - code: `import { defineConfig } from "@tanstack/react-start/config"; -import { wrapVinxiConfigWithSentry } from "@sentry/tanstackstart-react"; - -const config = defineConfig({ - // ... your other TanStack Start config -}) - -export default wrapVinxiConfigWithSentry(config, { - org: "${params.organization.slug}", - project: "${params.project.slug}", - authToken: process.env.SENTRY_AUTH_TOKEN, + filename: 'src/router.tsx', + code: `import * as Sentry from "@sentry/tanstackstart-react"; +import { createRouter } from '@tanstack/react-router' - // Only print logs for uploading source maps in CI - // Set to \`true\` to suppress logs - silent: !process.env.CI, -}); -`, - }, - ], - }, - { - type: 'text', - text: tct( - 'In future versions of this SDK, setting the [code:SENTRY_AUTH_TOKEN] environment variable during your build will upload sourcemaps for you to see unminified errors in Sentry.', - {code: } - ), - }, - { - type: 'code', - tabs: [ - { - language: 'bash', - label: 'Bash', - code: `SENTRY_AUTH_TOKEN=___ORG_AUTH_TOKEN___`, - }, - ], - }, - { - type: 'text', - text: tct( - 'Add the following initialization code to your [code:app/client.tsx] file to initialize Sentry on the client:', - {code: } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'TypeScript', - language: 'tsx', - filename: 'app/client.tsx', - code: `import { hydrateRoot } from "react-dom/client"; -import { StartClient } from "@tanstack/react-start"; -import { createRouter } from "./router"; +// Create a new router instance +export const getRouter = () => { + const router = createRouter(); -import * as Sentry from "@sentry/tanstackstart-react"; + if (!router.isServer) { + Sentry.init({ + dsn: "${params.dsn.public}", -const router = createRouter(); + // Adds request headers and IP for users, for more info visit: + // https://docs.sentry.io/platforms/javascript/guides/tanstackstart-react/configuration/options/#sendDefaultPii + sendDefaultPii: true,${ + params.isPerformanceSelected || params.isReplaySelected + ? ` -Sentry.init({ - dsn: "${params.dsn.public}",${ - params.isPerformanceSelected || params.isReplaySelected - ? ` - integrations: [${ - params.isPerformanceSelected - ? ` - Sentry.tanstackRouterBrowserTracingIntegration(router),` - : '' - }${ - params.isReplaySelected - ? ` - Sentry.replayIntegration(),` - : '' - } - ],` - : '' - }${ - params.isPerformanceSelected - ? ` + integrations: [${ + params.isPerformanceSelected + ? ` + Sentry.tanstackRouterBrowserTracingIntegration(router),` + : '' + }${ + params.isReplaySelected + ? ` + Sentry.replayIntegration(),` + : '' + } + ],` + : '' + }${ + params.isPerformanceSelected + ? ` - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for tracing. - tracesSampleRate: 1.0,` - : '' - }${ - params.isReplaySelected - ? ` + // 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,` + : '' + }${ + params.isReplaySelected + ? ` - // Capture Replay for 10% of all sessions, - // plus for 100% of sessions with an error. - replaysSessionSampleRate: 0.1, - replaysOnErrorSampleRate: 1.0,` - : '' + // Capture Replay for 10% of all sessions, + // plus for 100% of sessions with an error. + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0,` + : '' + } + }); } - // Setting this option to true will send default PII data to Sentry. - // For example, automatic IP address collection on events - sendDefaultPii: true, -}); - -hydrateRoot(document, );`, + return router; +}`, }, ], }, { type: 'text', text: tct( - 'Add the following initialization code anywhere in your [code:app/ssr.tsx] file to initialize Sentry on the server:', + 'Create an instrument file [code:instrument.server.mjs] in the root your project. In this file, initialize the Sentry SDK for your server:', {code: } ), }, @@ -180,26 +135,25 @@ hydrateRoot(document, );`, { label: 'TypeScript', language: 'tsx', - filename: 'app/ssr.tsx', + filename: 'instrument.server.mjs', code: `import * as Sentry from "@sentry/tanstackstart-react"; Sentry.init({ - dsn: "${params.dsn.public}",${ + dsn: "${params.dsn.public}", + + // Setting this option to true will send default PII data to Sentry. + // For example, automatic IP address collection on events + sendDefaultPii: true,${ params.isPerformanceSelected ? ` // 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 + // We recommend adjusting this value in production. + // Learn more at https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate tracesSampleRate: 1.0,` : '' } - - // Setting this option to true will send default PII data to Sentry. - // For example, automatic IP address collection on events - sendDefaultPii: true, });`, }, ], @@ -207,60 +161,44 @@ Sentry.init({ { type: 'text', text: tct( - "Wrap TanStack Start's [code:createRootRoute] function using [code:wrapCreateRootRouteWithSentry] to apply tracing to Server-Side Rendering (SSR):", - {code: } - ), - }, - { - type: 'code', - tabs: [ + 'For production monitoring, you need to move the Sentry server config file to your build output. Since [hostingLink:TanStack Start is designed to work with any hosting provider], the exact location will depend on where your build artifacts are deployed (for example, [code:/dist], [code:.output/server] or a platform-specific directory).', { - label: 'TypeScript', - language: 'tsx', - filename: 'app/routes/__root.tsx', - code: `import type { ReactNode } from "react"; -import { createRootRoute } from "@tanstack/react-router"; - -// (Wrap \`createRootRoute\`, not its return value!) -export const Route = wrapCreateRootRouteWithSentry(createRootRoute)({ - // ... -});`, - }, - ], + code: , + hostingLink: ( + + ), + } + ), }, { type: 'text', text: tct( - "Wrap TanStack Start's [code:defaultStreamHandler] function using [code:wrapStreamHandlerWithSentry] to instrument requests to your server:", - {code: } + 'For example, when using [nitroLink:Nitro], copy the instrumentation file to [code:.output/server]:', + { + code: , + nitroLink: , + } ), }, { type: 'code', tabs: [ { - label: 'TypeScript', - language: 'tsx', - filename: 'app/ssr.tsx', - code: `import { - createStartHandler, - defaultStreamHandler, -} from "@tanstack/react-start/server"; -import { getRouterManifest } from "@tanstack/react-start/router-manifest"; -import { createRouter } from "./router"; -import * as Sentry from "@sentry/tanstackstart-react"; - -export default createStartHandler({ - createRouter, - getRouterManifest, -})(Sentry.wrapStreamHandlerWithSentry(defaultStreamHandler));`, + label: 'JSON', + language: 'json', + filename: 'package.json', + code: `{ + "scripts": { + "build": "vite build && cp instrument.server.mjs .output/server", + } +}`, }, ], }, { type: 'text', text: tct( - "Add the [code:sentryGlobalServerMiddlewareHandler] to your global middlewares to instrument your server function invocations. If you haven't done so, create a [code:app/global-middleware.ts] file.", + 'Add a [code:--import] flag directly or to the [code:NODE_OPTIONS] environment variable wherever you run your application to import [code:instrument.server.mjs]:', {code: } ), }, @@ -268,33 +206,30 @@ export default createStartHandler({ type: 'code', tabs: [ { - label: 'TypeScript', - language: 'typescript', - filename: 'app/global-middleware.ts', - code: `import { - createMiddleware, - registerGlobalMiddleware, -} from "@tanstack/react-start"; -import * as Sentry from "@sentry/tanstackstart-react"; - -registerGlobalMiddleware({ - middleware: [ - createMiddleware().server(Sentry.sentryGlobalServerMiddlewareHandler()), - ], -});`, + label: 'JSON', + language: 'json', + filename: 'package.json', + code: `{ + "scripts": { + "build": "vite build && cp instrument.server.mjs .output/server", + "dev": "NODE_OPTIONS='--import ./instrument.server.mjs' vite dev --port 3000", + "start": "node --import ./.output/server/instrument.server.mjs .output/server/index.mjs", + } +}`, }, ], }, { type: 'text', - text: t( - 'The Sentry SDK cannot capture errors that you manually caught yourself with error boundaries.' + text: tct( + 'Sentry automatically captures unhandled client-side errors. On the server side of TanStack Start, automatic error monitoring is not yet supported. Use [code:captureException] to manually capture errors in your server-side code.', + {code: } ), }, { type: 'text', text: tct( - 'If you have React error boundaries in your app and you want to report errors that these boundaries catch to Sentry, apply the [code:withErrorBoundary] wrapper to your [code:ErrorBoundary]:', + "Errors caught by your own error boundaries aren't captured unless you report them manually. Wrap your custom [code:ErrorBoundary] component with [code:withErrorBoundary]:", {code: } ), }, @@ -350,10 +285,37 @@ const route = createRoute({ }, ], }, + { + type: 'text', + text: tct( + 'You can prevent ad blockers from blocking Sentry events using tunneling. Use the [code:tunnel] option to add an API endpoint in your application that forwards Sentry events to Sentry servers.', + {code: } + ), + }, + { + type: 'text', + text: tct( + 'To enable tunneling, update [code:Sentry.init] with the following option:', + {code: } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'TypeScript', + language: 'tsx', + code: `Sentry.init({ + dsn: "${params.dsn.public}", + tunnel: '/tunnel', +});`, + }, + ], + }, ], }, ], - verify: () => [ + verify: params => [ { type: StepType.VERIFY, content: [ @@ -366,7 +328,7 @@ const route = createRoute({ { type: 'text', text: t( - '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 one of your pages, which will trigger an error that Sentry will capture when you click it:' ), }, { @@ -388,46 +350,60 @@ const route = createRoute({ ], }, { - type: 'text', - text: tct( - 'To test tracing, create a test API route like /api/sentry-example-api:', - {code: } - ), - }, - { - type: 'code', - tabs: [ + type: 'conditional', + condition: params.isPerformanceSelected, + content: [ { - label: 'TypeScript', - language: 'typescript', - filename: 'app/routes/api/sentry-example-api.ts', - code: `import { json } from "@tanstack/react-start"; -import { createAPIFileRoute } from "@tanstack/react-start/api"; + type: 'text', + text: tct( + 'To test tracing, create a new file like [code:src/routes/api/sentry-example.ts] to create a test route [code:/api/sentry-example]:', + {code: } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'TypeScript', + language: 'typescript', + filename: 'src/app/routes/api/sentry-example-api.ts', + code: `import { createFileRoute } from "@tanstack/react-router"; +import { json } from "@tanstack/react-start"; -// A faulty API route to test Sentry's error monitoring -export const APIRoute = createAPIFileRoute("/api/sentry-example-api")({ - GET: ({ request, params }) => { - throw new Error("Sentry Example API Route Error"); - return json({ message: "Testing Sentry Error..." }); +export const Route = createFileRoute("/api/sentry-example")({ + server: { + handlers: { + GET: () => { + throw new Error("Sentry Example Route Error"); + return new Response( + JSON.stringify({ message: "Testing Sentry Error..." }), + { + headers: { + "Content-Type": "application/json", + }, + }, + ); + }, + }, }, });`, + }, + ], }, - ], - }, - { - type: 'text', - text: tct( - "Next, update your test button to call this route and throw an error if the response isn't [code:ok]:", - {code: } - ), - }, - { - type: 'code', - tabs: [ { - label: 'TypeScript', - language: 'tsx', - code: ``, +;`, + }, + ], + }, + { + type: 'text', + text: t( + 'Open the page in a browser and click the button to trigger two errors:' + ), + }, + { + type: 'custom', + content: ( +
    +
  • {t('a frontend error')}
  • +
  • {t('an error within the API route')}
  • +
+ ), + }, + { + type: 'text', + text: t( + 'Additionally, this starts a performance trace to measure the time it takes for the API request to complete.' + ), }, ], }, { - type: 'text', - text: t( - 'Open the page in a browser and click the button to trigger two errors:' - ), - }, - { - type: 'custom', - content: ( -
    -
  • {t('a frontend error')}
  • -
  • {t('an error within the API route')}
  • -
- ), + type: 'conditional', + condition: !params.isPerformanceSelected, + content: [ + { + type: 'text', + text: t( + 'Open the page in a browser and click the button to trigger a frontend error.' + ), + }, + ], }, { type: 'text', text: t( - 'Additionally, this starts a performance trace to measure the time it takes for the API request to complete.' + 'Now view the collected data in your issues feed (it takes a couple of moments for the data to appear).' ), }, ],