From 7a46a019cbb88403ff51abfd5602c6e373c879bc Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Tue, 13 Oct 2026 09:49:14 +0200 Subject: [PATCH 1/5] docs(js): [POC A] Turn server framework guides into landing pages Express, Fastify, Koa, Hapi and Hono no longer inherit the common JavaScript pages. Each guide is now one landing page that links to the runtime quick starts (Node.js, Bun, Deno, and Cloudflare for Hono), because the runtime SDKs instrument these frameworks automatically. Old subpages redirect to their Node.js equivalents. The Fastify shouldHandleError docs moved to the Fastify integration page. Co-Authored-By: Claude Opus 5.5 --- .../configuration/integrations/fastify.mdx | 41 ++ .../data-management/sensitive-data/index.mdx | 2 +- .../common/sourcemaps/uploading/rollup.mdx | 2 +- .../guides/cloudflare/frameworks/hono.mdx | 2 +- .../javascript/guides/cloudflare/index.mdx | 2 +- .../data-management/sensitive-data/index.mdx | 2 +- .../cordova/sourcemaps/uploading/rollup.mdx | 2 +- .../integrations/contextlines.mdx | 20 - .../javascript/guides/express/index.mdx | 44 +- .../javascript/guides/express/index__v7.x.mdx | 14 - .../automatic-instrumentation.mdx | 6 - .../guides/fastify/features/error-handler.mdx | 57 --- .../guides/fastify/features/index.mdx | 9 - .../javascript/guides/fastify/index.mdx | 27 +- .../automatic-instrumentation.mdx | 6 - .../integrations/contextlines.mdx | 20 - .../javascript/guides/hapi/index.mdx | 25 +- .../automatic-instrumentation.mdx | 6 - .../guides/hono/features/error-handler.mdx | 102 ---- .../javascript/guides/hono/features/index.mdx | 9 - .../javascript/guides/hono/index.mdx | 437 ++---------------- .../integrations/contextlines.mdx | 20 - .../platforms/javascript/guides/koa/index.mdx | 25 +- .../automatic-instrumentation.mdx | 6 - docs/product/logs/getting-started/index.mdx | 10 +- docs/product/mcp-servers/getting-started.mdx | 2 +- .../product/metrics/getting-started/index.mdx | 10 +- redirects.js | 30 +- 28 files changed, 240 insertions(+), 698 deletions(-) delete mode 100644 docs/platforms/javascript/guides/express/configuration/integrations/contextlines.mdx delete mode 100644 docs/platforms/javascript/guides/express/index__v7.x.mdx delete mode 100644 docs/platforms/javascript/guides/express/tracing/instrumentation/automatic-instrumentation.mdx delete mode 100644 docs/platforms/javascript/guides/fastify/features/error-handler.mdx delete mode 100644 docs/platforms/javascript/guides/fastify/features/index.mdx delete mode 100644 docs/platforms/javascript/guides/fastify/tracing/instrumentation/automatic-instrumentation.mdx delete mode 100644 docs/platforms/javascript/guides/hapi/configuration/integrations/contextlines.mdx delete mode 100644 docs/platforms/javascript/guides/hapi/tracing/instrumentation/automatic-instrumentation.mdx delete mode 100644 docs/platforms/javascript/guides/hono/features/error-handler.mdx delete mode 100644 docs/platforms/javascript/guides/hono/features/index.mdx delete mode 100644 docs/platforms/javascript/guides/koa/configuration/integrations/contextlines.mdx delete mode 100644 docs/platforms/javascript/guides/koa/tracing/instrumentation/automatic-instrumentation.mdx diff --git a/docs/platforms/javascript/common/configuration/integrations/fastify.mdx b/docs/platforms/javascript/common/configuration/integrations/fastify.mdx index f4ba56683c2d8..cf140e80b1134 100644 --- a/docs/platforms/javascript/common/configuration/integrations/fastify.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/fastify.mdx @@ -19,6 +19,47 @@ Sentry.init({ }); ``` +## Options + +### `shouldHandleError` + +A function that decides whether an error is captured. By default, errors with a 5xx status code (and errors with a 2xx status code or lower) are captured, and errors with a 3xx or 4xx status code aren't. + +```typescript +declare function shouldHandleError( + error: Error, + request: FastifyRequest, + reply: FastifyReply +): boolean; +``` + +```javascript +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [ + Sentry.fastifyIntegration({ + shouldHandleError(error, request, reply) { + return reply.statusCode >= 500; + }, + }), + ], +}); +``` + +If you use TypeScript, cast the request and reply to get full type safety: + +```typescript +import type { FastifyRequest, FastifyReply } from "fastify"; + +Sentry.fastifyIntegration({ + shouldHandleError(error, minimalRequest, minimalReply) { + const request = minimalRequest as FastifyRequest; + const reply = minimalReply as FastifyReply; + return reply.statusCode >= 500; + }, +}); +``` + ## Supported Versions - `fastify`: `^3.21.0 || ^4.0.0` diff --git a/docs/platforms/javascript/common/data-management/sensitive-data/index.mdx b/docs/platforms/javascript/common/data-management/sensitive-data/index.mdx index b5c70dd09409f..dae753d55eacb 100644 --- a/docs/platforms/javascript/common/data-management/sensitive-data/index.mdx +++ b/docs/platforms/javascript/common/data-management/sensitive-data/index.mdx @@ -1,7 +1,7 @@ --- title: "Scrubbing Sensitive Data" description: "Learn about filtering or scrubbing sensitive data within the Sentry JavaScript SDK, so that data is not sent with the event. You can also configure server-side scrubbing to ensure the data is not stored." -customCanonicalTag: "/platforms/javascript/guides/koa/data-management/sensitive-data/" +customCanonicalTag: "/platforms/javascript/guides/node/data-management/sensitive-data/" sidebar_order: 100 keywords: - pii diff --git a/docs/platforms/javascript/common/sourcemaps/uploading/rollup.mdx b/docs/platforms/javascript/common/sourcemaps/uploading/rollup.mdx index 29b44acd07dd1..3c8f4aac66d5c 100644 --- a/docs/platforms/javascript/common/sourcemaps/uploading/rollup.mdx +++ b/docs/platforms/javascript/common/sourcemaps/uploading/rollup.mdx @@ -2,7 +2,7 @@ title: Rollup description: "Upload your source maps with the Sentry Rollup Plugin." sidebar_order: 5 -customCanonicalTag: "/platforms/javascript/guides/koa/sourcemaps/uploading/rollup/" +customCanonicalTag: "/platforms/javascript/guides/node/sourcemaps/uploading/rollup/" --- diff --git a/docs/platforms/javascript/guides/cloudflare/frameworks/hono.mdx b/docs/platforms/javascript/guides/cloudflare/frameworks/hono.mdx index 6a7df59814d50..0dc23f392abd8 100644 --- a/docs/platforms/javascript/guides/cloudflare/frameworks/hono.mdx +++ b/docs/platforms/javascript/guides/cloudflare/frameworks/hono.mdx @@ -67,4 +67,4 @@ To read the DSN from a Worker binding, pass a callback instead of a plain option app.use(sentry(app, (env) => ({ dsn: env.SENTRY_DSN }))); ``` -For the full setup, including verification and the other runtimes, see the **[Hono Quick Start Guide](/platforms/javascript/guides/hono/)**. +To set up Sentry for Hono on other runtimes, see [Hono](/platforms/javascript/guides/hono/). diff --git a/docs/platforms/javascript/guides/cloudflare/index.mdx b/docs/platforms/javascript/guides/cloudflare/index.mdx index 07bc0e6562d4e..033ffc7562e97 100644 --- a/docs/platforms/javascript/guides/cloudflare/index.mdx +++ b/docs/platforms/javascript/guides/cloudflare/index.mdx @@ -15,7 +15,7 @@ This guide covers Cloudflare Workers. If you're deploying a Cloudflare Pages app If you're using any of the listed frameworks, follow their specific setup instructions: - **[Astro](/platforms/javascript/guides/cloudflare/frameworks/astro/)** -- **[Hono](/platforms/javascript/guides/hono/)** (with @sentry/hono) +- **[Hono](/platforms/javascript/guides/cloudflare/frameworks/hono/)** (with @sentry/hono) - **[Hydrogen](/platforms/javascript/guides/cloudflare/frameworks/hydrogen-react-router/)** - **[Next.js](/platforms/javascript/guides/cloudflare/frameworks/nextjs/)** - **[Nuxt](/platforms/javascript/guides/cloudflare/frameworks/nuxt/)** diff --git a/docs/platforms/javascript/guides/cordova/data-management/sensitive-data/index.mdx b/docs/platforms/javascript/guides/cordova/data-management/sensitive-data/index.mdx index 5045b48f990e1..5a280d3a5710c 100644 --- a/docs/platforms/javascript/guides/cordova/data-management/sensitive-data/index.mdx +++ b/docs/platforms/javascript/guides/cordova/data-management/sensitive-data/index.mdx @@ -1,7 +1,7 @@ --- title: "Scrubbing Sensitive Data" description: "Learn about filtering or scrubbing sensitive data within the Sentry JavaScript SDK, so that data is not sent with the event. You can also configure server-side scrubbing to ensure the data is not stored." -customCanonicalTag: "/platforms/javascript/guides/koa/data-management/sensitive-data/" +customCanonicalTag: "/platforms/javascript/guides/node/data-management/sensitive-data/" sidebar_order: 100 keywords: - pii diff --git a/docs/platforms/javascript/guides/cordova/sourcemaps/uploading/rollup.mdx b/docs/platforms/javascript/guides/cordova/sourcemaps/uploading/rollup.mdx index 29b44acd07dd1..3c8f4aac66d5c 100644 --- a/docs/platforms/javascript/guides/cordova/sourcemaps/uploading/rollup.mdx +++ b/docs/platforms/javascript/guides/cordova/sourcemaps/uploading/rollup.mdx @@ -2,7 +2,7 @@ title: Rollup description: "Upload your source maps with the Sentry Rollup Plugin." sidebar_order: 5 -customCanonicalTag: "/platforms/javascript/guides/koa/sourcemaps/uploading/rollup/" +customCanonicalTag: "/platforms/javascript/guides/node/sourcemaps/uploading/rollup/" --- diff --git a/docs/platforms/javascript/guides/express/configuration/integrations/contextlines.mdx b/docs/platforms/javascript/guides/express/configuration/integrations/contextlines.mdx deleted file mode 100644 index 7b3af58727490..0000000000000 --- a/docs/platforms/javascript/guides/express/configuration/integrations/contextlines.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: ContextLines -description: "Adds source code to your stack frames. (default)" ---- - -_Import name: `Sentry.contextLinesIntegration`_ - -This integration is enabled by default. If you'd like to modify your default integrations, read [this](./../#modifying-default-integrations). - -This integration adds source code to your stack frames. It ensures that you can see the code that caused the error directly in the Sentry UI. - - - -## Options - -### `frameContextLines` - -_Type: `number`_ - -The number of lines to collect around each stack frame's line number. Defaults to 7. diff --git a/docs/platforms/javascript/guides/express/index.mdx b/docs/platforms/javascript/guides/express/index.mdx index c495ee6e41d74..d7cdeba89b349 100644 --- a/docs/platforms/javascript/guides/express/index.mdx +++ b/docs/platforms/javascript/guides/express/index.mdx @@ -1,11 +1,51 @@ --- title: Express -description: "Learn how to manually set up Sentry in your Express app and capture your first errors." +description: "Learn how to set up Sentry in your Express app on Node.js, Bun, or Deno." sdk: sentry.javascript.node fallbackGuide: javascript.node +inheritCommonContent: false categories: - server - server-only --- - +[Express](https://expressjs.com/) doesn't need its own Sentry SDK. Set up Sentry for the runtime your app runs on, and the SDK instruments Express automatically: `expressIntegration` captures errors from your routes and middleware, and creates spans for incoming requests when tracing is enabled. + +## Set Up Sentry for Your Runtime + +- +- +- + +## Customize Error Capturing + +Errors with a 3xx or 4xx status code are skipped, since they're usually expected redirects or client errors. To change which errors are captured, pass a `shouldHandleError` callback to `expressIntegration()`: + +```javascript +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [ + Sentry.expressIntegration({ + shouldHandleError(error) { + const status = Number(error.status ?? error.statusCode ?? 500); + // Capture 401/403 in addition to the default 5xx errors + return status === 401 || status === 403 || status >= 500; + }, + }), + ], +}); +``` + +If you capture errors yourself in your own error-handling middleware, set `shouldHandleError: false` to avoid duplicate events. diff --git a/docs/platforms/javascript/guides/express/index__v7.x.mdx b/docs/platforms/javascript/guides/express/index__v7.x.mdx deleted file mode 100644 index 76ef56f70dc76..0000000000000 --- a/docs/platforms/javascript/guides/express/index__v7.x.mdx +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Express -description: "Learn how to instrument the Sentry Express SDK." -sdk: sentry.javascript.node -fallbackGuide: javascript.node -categories: - - server ---- - - - -This guide explains how to set up Sentry in your Express application. - - diff --git a/docs/platforms/javascript/guides/express/tracing/instrumentation/automatic-instrumentation.mdx b/docs/platforms/javascript/guides/express/tracing/instrumentation/automatic-instrumentation.mdx deleted file mode 100644 index c7b42fb602ab9..0000000000000 --- a/docs/platforms/javascript/guides/express/tracing/instrumentation/automatic-instrumentation.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Automatic Instrumentation -description: "Learn what transactions are captured after tracing is enabled in the Sentry Express SDK." ---- - - diff --git a/docs/platforms/javascript/guides/fastify/features/error-handler.mdx b/docs/platforms/javascript/guides/fastify/features/error-handler.mdx deleted file mode 100644 index 43e3c0f8cf935..0000000000000 --- a/docs/platforms/javascript/guides/fastify/features/error-handler.mdx +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Fastify Error Handler -description: "Learn how Sentry's Fastify integration captures errors and how to configure it." ---- - -The `fastifyIntegration` captures errors in your Fastify application and sends them to Sentry. It's enabled by default, so you only need to add it to your `Sentry.init` call to configure it. - -By default, errors with status codes 5xx and above, as well as errors with status codes 2xx and below, are captured. Errors with 3xx and 4xx status codes aren't sent to Sentry. - -## Configuration - -To control which errors are captured, pass `shouldHandleError` to `Sentry.fastifyIntegration`: - -```javascript -import * as Sentry from "@sentry/node"; - -Sentry.init({ - dsn: "your-dsn", - integrations: [ - Sentry.fastifyIntegration({ - shouldHandleError(error, request, reply) { - return reply.statusCode >= 500; - }, - }), - ], -}); -``` - -## Options - -`Sentry.fastifyIntegration` accepts the following options: - -- `shouldHandleError` - -A function that determines whether an error should be captured. - -```typescript -declare function shouldHandleError( - error: Error, - request: FastifyRequest, - reply: FastifyReply -): boolean; -``` - -If using TypeScript, you can cast the request and reply to get full type safety. - -```typescript -import type { FastifyRequest, FastifyReply } from "fastify"; - -Sentry.fastifyIntegration({ - shouldHandleError(error, minimalRequest, minimalReply) { - const request = minimalRequest as FastifyRequest; - const reply = minimalReply as FastifyReply; - return reply.statusCode >= 500; - }, -}); -``` diff --git a/docs/platforms/javascript/guides/fastify/features/index.mdx b/docs/platforms/javascript/guides/fastify/features/index.mdx deleted file mode 100644 index b7fcfb338bb49..0000000000000 --- a/docs/platforms/javascript/guides/fastify/features/index.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Fastify Features -description: "Learn how Sentry's Node SDK exposes features for first class integration with Fastify." -excerpt: "" ---- - -The Sentry Node SDK offers Fastify-specific features for first class integration with the framework. - - diff --git a/docs/platforms/javascript/guides/fastify/index.mdx b/docs/platforms/javascript/guides/fastify/index.mdx index d7aecbc64fde8..3159223afd40d 100644 --- a/docs/platforms/javascript/guides/fastify/index.mdx +++ b/docs/platforms/javascript/guides/fastify/index.mdx @@ -1,13 +1,34 @@ --- title: Fastify -description: "Learn how to manually set up Sentry in your Fastify app and capture your first errors." +description: "Learn how to set up Sentry in your Fastify app on Node.js, Bun, or Deno." sdk: sentry.javascript.node fallbackGuide: javascript.node +inheritCommonContent: false categories: - server - server-only --- - +[Fastify](https://fastify.dev/) doesn't need its own Sentry SDK. Set up Sentry for the runtime your app runs on, and the SDK instruments Fastify automatically: `fastifyIntegration` captures errors from your routes and hooks, and creates spans for incoming requests when tracing is enabled. - +## Set Up Sentry for Your Runtime + +- +- +- + +## Customize Error Capturing + +Errors with a 3xx or 4xx status code are skipped. To change which errors are captured, pass a `shouldHandleError` callback to `fastifyIntegration()`. See the [Fastify integration](/platforms/javascript/guides/node/configuration/integrations/fastify/#shouldhandleerror) for all options. diff --git a/docs/platforms/javascript/guides/fastify/tracing/instrumentation/automatic-instrumentation.mdx b/docs/platforms/javascript/guides/fastify/tracing/instrumentation/automatic-instrumentation.mdx deleted file mode 100644 index abc98b5bca4fd..0000000000000 --- a/docs/platforms/javascript/guides/fastify/tracing/instrumentation/automatic-instrumentation.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Automatic Instrumentation -description: "Learn what transactions are captured after tracing is enabled in the Sentry Fastify SDK." ---- - - diff --git a/docs/platforms/javascript/guides/hapi/configuration/integrations/contextlines.mdx b/docs/platforms/javascript/guides/hapi/configuration/integrations/contextlines.mdx deleted file mode 100644 index 7b3af58727490..0000000000000 --- a/docs/platforms/javascript/guides/hapi/configuration/integrations/contextlines.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: ContextLines -description: "Adds source code to your stack frames. (default)" ---- - -_Import name: `Sentry.contextLinesIntegration`_ - -This integration is enabled by default. If you'd like to modify your default integrations, read [this](./../#modifying-default-integrations). - -This integration adds source code to your stack frames. It ensures that you can see the code that caused the error directly in the Sentry UI. - - - -## Options - -### `frameContextLines` - -_Type: `number`_ - -The number of lines to collect around each stack frame's line number. Defaults to 7. diff --git a/docs/platforms/javascript/guides/hapi/index.mdx b/docs/platforms/javascript/guides/hapi/index.mdx index d7196e2d78d27..aa00c6ae39366 100644 --- a/docs/platforms/javascript/guides/hapi/index.mdx +++ b/docs/platforms/javascript/guides/hapi/index.mdx @@ -1,11 +1,32 @@ --- title: Hapi -description: "Learn how to manually set up Sentry in your Hapi app and capture your first errors." +description: "Learn how to set up Sentry in your Hapi app on Node.js, Bun, or Deno." sdk: sentry.javascript.node fallbackGuide: javascript.node +inheritCommonContent: false categories: - server - server-only --- - +[Hapi](https://hapi.dev/) doesn't need its own Sentry SDK. Set up Sentry for the runtime your app runs on, and the SDK instruments Hapi automatically: `hapiIntegration` registers an error handler when your server starts, and creates spans for incoming requests when tracing is enabled. + +## Set Up Sentry for Your Runtime + +- +- +- + +To configure the integration, see the [Hapi integration](/platforms/javascript/guides/node/configuration/integrations/hapi/). diff --git a/docs/platforms/javascript/guides/hapi/tracing/instrumentation/automatic-instrumentation.mdx b/docs/platforms/javascript/guides/hapi/tracing/instrumentation/automatic-instrumentation.mdx deleted file mode 100644 index e749b53b702b1..0000000000000 --- a/docs/platforms/javascript/guides/hapi/tracing/instrumentation/automatic-instrumentation.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Automatic Instrumentation -description: "Learn what transactions are captured after tracing is enabled in the Sentry Hapi SDK." ---- - - diff --git a/docs/platforms/javascript/guides/hono/features/error-handler.mdx b/docs/platforms/javascript/guides/hono/features/error-handler.mdx deleted file mode 100644 index 9167f06300b59..0000000000000 --- a/docs/platforms/javascript/guides/hono/features/error-handler.mdx +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: Error Handler -description: "Learn how Sentry's Hono SDK captures errors and how to customize which errors are sent to Sentry." ---- - -The `sentry()` middleware automatically captures unhandled errors from Hono's `onError` handler. - -## Default Behavior - -The default logic checks whether the error carries a numeric `status` property (as Hono's `HTTPException` and similar error types do). - -The middleware captures: - -- **5xx errors** — anything with a numeric `status >= 500` -- **Non-HTTP errors** — errors without a numeric `status` property, including standard `Error` instances, non-object throws, and `null` - -The middleware ignores: - -- **3xx–4xx errors** — anything with a numeric `status` in the range 300–499 (redirects, client errors) - -Use `shouldHandleError` when you need to capture errors outside this default range, for example to track 401 and 403 responses as security signals. - -## Using `shouldHandleError` - -Pass a `shouldHandleError` callback to the `sentry()` middleware. The callback receives the raw error as `unknown` and must return `true` to capture it or `false` to suppress it. - -```typescript {tabTitle:Cloudflare Workers} {filename:index.ts} {mdExpandTabs} -import { Hono } from "hono"; -import { sentry } from "@sentry/hono/cloudflare"; - -const app = new Hono(); - -app.use( - sentry(app, { - dsn: "___PUBLIC_DSN___", - shouldHandleError(error) { - const status = (error as { status?: number })?.status; - // Capture 401/403 in addition to the default 5xx errors - return ( - status === 401 || - status === 403 || - typeof status !== "number" || - status >= 500 - ); - }, - }) -); - -export default app; -``` - -```typescript {tabTitle:Node.js} {filename:app.ts} -import { Hono } from "hono"; -import { serve } from "@hono/node-server"; -import { sentry } from "@sentry/hono/node"; - -const app = new Hono(); - -app.use( - sentry(app, { - shouldHandleError(error) { - const status = (error as { status?: number })?.status; - // Capture 401/403 in addition to the default 5xx errors - return ( - status === 401 || - status === 403 || - typeof status !== "number" || - status >= 500 - ); - }, - }) -); - -// Your routes here - -serve(app); -``` - -```typescript {tabTitle:Bun} {filename:index.ts} -import { Hono } from "hono"; -import { sentry } from "@sentry/hono/bun"; - -const app = new Hono(); - -app.use( - sentry(app, { - dsn: "___PUBLIC_DSN___", - shouldHandleError(error) { - const status = (error as { status?: number })?.status; - // Capture 401/403 in addition to the default 5xx errors - return ( - status === 401 || - status === 403 || - typeof status !== "number" || - status >= 500 - ); - }, - }) -); - -export default app; -``` diff --git a/docs/platforms/javascript/guides/hono/features/index.mdx b/docs/platforms/javascript/guides/hono/features/index.mdx deleted file mode 100644 index 0580f8f8c3f5d..0000000000000 --- a/docs/platforms/javascript/guides/hono/features/index.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Hono Features -description: "Learn how Sentry's Hono SDK exposes features for first-class integration with Hono." -excerpt: "" ---- - -The Sentry Hono SDK offers Hono-specific features for first-class integration with the framework. - - diff --git a/docs/platforms/javascript/guides/hono/index.mdx b/docs/platforms/javascript/guides/hono/index.mdx index 71ad13f56a967..9b6832acaf3e6 100644 --- a/docs/platforms/javascript/guides/hono/index.mdx +++ b/docs/platforms/javascript/guides/hono/index.mdx @@ -1,404 +1,59 @@ --- title: Hono -description: "Learn how to set up Sentry in your Hono app and capture your first errors." +description: "Learn how to set up Sentry in your Hono app on Node.js, Bun, Deno, or Cloudflare Workers." sdk: sentry.javascript.hono fallbackGuide: javascript.node +inheritCommonContent: false categories: - server - server-only --- -The `@sentry/hono` SDK supports Hono 4+ across multiple runtimes: Cloudflare Workers, Node.js, Bun, and Deno. It works as Hono middleware, so you can drop it into your existing app with minimal setup. - - - -The community middleware `@hono/sentry` has been deprecated in favor of `@sentry/hono`, which provides better performance and more features. To migrate: - -1. Uninstall `@hono/sentry` and `toucan-js` -2. Follow the install and configure steps below for your runtime -3. Replace the old middleware with the new `sentry()` middleware - - - - - - - -## Install - -Choose the features you want to configure, and this guide will show you how: - - - - - -### Install the Sentry SDK - - - - - -Install the `@sentry/hono` package: - - - - -```bash {tabTitle:npm} -npm install @sentry/hono -``` - -```bash {tabTitle:yarn} -yarn add @sentry/hono -``` - -```bash {tabTitle:pnpm} -pnpm add @sentry/hono -``` - - - - - -### Install the Peer Dependency for Your Runtime - - - - - -The SDK needs a runtime-specific peer dependency. Install the one that matches your environment and keep its version in sync with `@sentry/hono`. You won't import it directly, but it must be present in your project. - - - - -```bash {tabTitle:Cloudflare Workers} {mdExpandTabs} -npm install @sentry/cloudflare -``` - -```bash {tabTitle:Node.js} -npm install @sentry/node -``` - -```bash {tabTitle:Bun} -npm install @sentry/bun -``` - -```bash {tabTitle:Deno} -deno add npm:@sentry/deno -``` - - - - - - - -### Install the Profiling Package (Node.js Only) - - - - - -Profiling is only available for the Node.js runtime. Install the `@sentry/profiling-node` package: - - - - -```bash {tabTitle:npm} -npm install @sentry/profiling-node --save -``` - -```bash {tabTitle:yarn} -yarn add @sentry/profiling-node -``` - -```bash {tabTitle:pnpm} -pnpm add @sentry/profiling-node -``` - - - - - - - -## Configure - - - - - - - -If you're deploying to Cloudflare Workers, set the `nodejs_compat` compatibility flag. The SDK needs `AsyncLocalStorage`, which requires this flag. - - - - -```jsonc {tabTitle:JSON} {filename:wrangler.jsonc} -{ - "compatibility_flags": ["nodejs_compat"], -} -``` - -```toml {tabTitle:Toml} {filename:wrangler.toml} -compatibility_flags = ["nodejs_compat"] -``` - - - - - - - - - - - - - -Node.js requires Sentry to initialize before your application loads so it can instrument third-party libraries (like database clients). Create a separate initialization file: - - - - -```typescript {filename:instrument.mjs} -// IMPORTANT: import from `@sentry/hono/node`, not `@sentry/node` -import * as Sentry from "@sentry/hono/node"; -// ___PRODUCT_OPTION_START___ profiling -import { nodeProfilingIntegration } from "@sentry/profiling-node"; -// ___PRODUCT_OPTION_END___ profiling - +[Hono](https://hono.dev/) doesn't need its own Sentry setup. Set up Sentry for the runtime your app runs on, and the SDK instruments Hono automatically: `honoIntegration` captures errors from Hono's `onError` handler, and creates spans for incoming requests when tracing is enabled. + +## Set Up Sentry for Your Runtime + +- +- +- +- + +## Customize Error Capturing + +Errors with a 3xx or 4xx status code are skipped. To change which errors are captured, pass a `shouldHandleError` callback to `honoIntegration()`: + +```javascript Sentry.init({ dsn: "___PUBLIC_DSN___", - - // ___PRODUCT_OPTION_START___ profiling - - integrations: [nodeProfilingIntegration()], - // ___PRODUCT_OPTION_END___ profiling - // ___PRODUCT_OPTION_START___ performance - - // Set tracesSampleRate to 1.0 to capture 100% - // of spans for tracing. - // We recommend adjusting this value in production - // Learn more at - // https://docs.sentry.io/platforms/javascript/guides/hono/configuration/options/#tracesSampleRate - tracesSampleRate: 1.0, - // ___PRODUCT_OPTION_END___ performance - // ___PRODUCT_OPTION_START___ profiling - - // Enable profiling for a percentage of sessions - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#profileSessionSampleRate - profileSessionSampleRate: 1.0, - // Profile automatically while there's an active sampled span - profileLifecycle: "trace", - // ___PRODUCT_OPTION_END___ profiling + integrations: [ + Sentry.honoIntegration({ + shouldHandleError(error) { + const status = error?.status; + // Capture 401/403 in addition to the default 5xx errors + return ( + status === 401 || + status === 403 || + typeof status !== "number" || + status >= 500 + ); + }, + }), + ], }); ``` - - - - - - - - - -Load this file with the `--import` flag when starting your app. You can also set this as an environment variable instead: - - - - -```bash {tabTitle:CLI Option} -node --import ./instrument.mjs app.js -``` - -```bash {tabTitle:ENV Variable} -NODE_OPTIONS="--import ./instrument.mjs" -``` - - - - - - - -### Add the Sentry Middleware - - - - - -Add the `sentry()` middleware as early as possible in your Hono app. - -Before continuing, make sure you've completed the runtime-specific steps above: **Cloudflare Workers** users need to enable Node.js compatibility, and **Node.js** users need to create the instrument file. Since Node.js initializes Sentry in that file, the middleware doesn't take any options. - - - - -```typescript {tabTitle:Cloudflare Workers} {filename:index.ts} {mdExpandTabs} -import { Hono } from "hono"; -import { sentry } from "@sentry/hono/cloudflare"; - -const app = new Hono(); - -app.use( - sentry(app, { - dsn: "___PUBLIC_DSN___", - - // ___PRODUCT_OPTION_START___ performance - - // Set tracesSampleRate to 1.0 to capture 100% - // of spans for tracing. - tracesSampleRate: 1.0, - // ___PRODUCT_OPTION_END___ performance - }) -); - -// Your routes here - -export default app; -``` - -```typescript {tabTitle:Node.js} {filename:app.ts} -import { Hono } from "hono"; -import { serve } from "@hono/node-server"; -import { sentry } from "@sentry/hono/node"; - -const app = new Hono(); - -app.use(sentry(app)); - -// Your routes here - -serve(app); -``` - -```typescript {tabTitle:Bun} {filename:index.ts} -import { Hono } from "hono"; -import { sentry } from "@sentry/hono/bun"; - -const app = new Hono(); - -app.use( - sentry(app, { - dsn: "___PUBLIC_DSN___", - - // ___PRODUCT_OPTION_START___ performance - - // Set tracesSampleRate to 1.0 to capture 100% - // of spans for tracing. - tracesSampleRate: 1.0, - // ___PRODUCT_OPTION_END___ performance - }) -); - -// Your routes here - -export default app; -``` - -```typescript {tabTitle:Deno} {filename:index.ts} -import { Hono } from "hono"; -import { sentry } from "@sentry/hono/deno"; - -const app = new Hono(); - -app.use( - sentry(app, { - dsn: "___PUBLIC_DSN___", - - // ___PRODUCT_OPTION_START___ performance - - // Set tracesSampleRate to 1.0 to capture 100% - // of spans for tracing. - tracesSampleRate: 1.0, - // ___PRODUCT_OPTION_END___ performance - }) -); - -// Your routes here - -Deno.serve(app.fetch); -``` - - - - - - - - - - - -To access environment variables from your Cloudflare Worker bindings (for example, to store the DSN as a secret), pass a callback instead of a plain options object. The function receives the Worker `env`: - - - - -```typescript {filename:index.ts} -import { Hono } from "hono"; -import { sentry } from "@sentry/hono/cloudflare"; - -type Bindings = { SENTRY_DSN: string }; - -const app = new Hono<{ Bindings: Bindings }>(); - -app.use(sentry(app, (env) => ({ dsn: env.SENTRY_DSN }))); - -export default app; -``` - - - - - - - -### Report Unhandled Exceptions - -By default, Sentry captures exceptions from Hono's `onError` handler, excluding errors with 3xx or 4xx status codes. No additional configuration is needed for this. - -To control which errors are captured, use the `shouldHandleError` option. - -### Add Readable Stack Traces With Source Maps (Optional) - - - -### Control the Data You Send to Sentry (Optional) - - - -## Verify Your Setup - - - -### 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 Sentry integrated into your Hono application and already be sending data to your Sentry project. - -Now's a good time to customize your setup and look into more advanced topics: - -- Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup -- Learn how to manually capture errors -- Explore Hono-specific features like custom error filtering -- Continue to customize your configuration -- Get familiar with [Sentry's product features](/product/) like tracing, insights, and alerts - - - -- Find various topics in Troubleshooting -- [Get support](https://www.sentry.help/en/) - - - - diff --git a/docs/platforms/javascript/guides/koa/configuration/integrations/contextlines.mdx b/docs/platforms/javascript/guides/koa/configuration/integrations/contextlines.mdx deleted file mode 100644 index 7b3af58727490..0000000000000 --- a/docs/platforms/javascript/guides/koa/configuration/integrations/contextlines.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: ContextLines -description: "Adds source code to your stack frames. (default)" ---- - -_Import name: `Sentry.contextLinesIntegration`_ - -This integration is enabled by default. If you'd like to modify your default integrations, read [this](./../#modifying-default-integrations). - -This integration adds source code to your stack frames. It ensures that you can see the code that caused the error directly in the Sentry UI. - - - -## Options - -### `frameContextLines` - -_Type: `number`_ - -The number of lines to collect around each stack frame's line number. Defaults to 7. diff --git a/docs/platforms/javascript/guides/koa/index.mdx b/docs/platforms/javascript/guides/koa/index.mdx index f8c201fc2708a..46c24b474fa70 100644 --- a/docs/platforms/javascript/guides/koa/index.mdx +++ b/docs/platforms/javascript/guides/koa/index.mdx @@ -1,11 +1,32 @@ --- title: Koa -description: "Learn how to manually set up Sentry in your Koa app and capture your first errors." +description: "Learn how to set up Sentry in your Koa app on Node.js, Bun, or Deno." sdk: sentry.javascript.node fallbackGuide: javascript.node +inheritCommonContent: false categories: - server - server-only --- - +[Koa](https://koajs.com/) doesn't need its own Sentry SDK. Set up Sentry for the runtime your app runs on, and the SDK instruments Koa automatically: `koaIntegration` registers an error handler when your app starts, and creates spans for incoming requests and middleware when tracing is enabled. + +## Set Up Sentry for Your Runtime + +- +- +- + +To configure the integration, see the [Koa integration](/platforms/javascript/guides/node/configuration/integrations/koa/). diff --git a/docs/platforms/javascript/guides/koa/tracing/instrumentation/automatic-instrumentation.mdx b/docs/platforms/javascript/guides/koa/tracing/instrumentation/automatic-instrumentation.mdx deleted file mode 100644 index 4ad82b17e57af..0000000000000 --- a/docs/platforms/javascript/guides/koa/tracing/instrumentation/automatic-instrumentation.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Automatic Instrumentation -description: "Learn what transactions are captured after tracing is enabled in the Sentry Koa SDK." ---- - - diff --git a/docs/product/logs/getting-started/index.mdx b/docs/product/logs/getting-started/index.mdx index abe253815930a..7f38e91cdf089 100644 --- a/docs/product/logs/getting-started/index.mdx +++ b/docs/product/logs/getting-started/index.mdx @@ -68,13 +68,13 @@ To set up Sentry Logs, use the links below for supported SDKs. After it's been s - - - - - - - - - - - Date: Wed, 14 Oct 2026 11:12:08 +0200 Subject: [PATCH 2/5] docs: [POC A] Remove 2 pages that redirects hide /concepts/key-terms/tracing/event-detail/ (an empty file) and the draft /platforms/javascript/legacy-sdk/integrations/ page are built, but the redirects in middleware.ts send every visitor away from them. Co-Authored-By: Claude Opus 5.5 --- .../key-terms/tracing/event-detail.mdx | 0 .../javascript/legacy-sdk/integrations.mdx | 696 ------------------ 2 files changed, 696 deletions(-) delete mode 100644 docs/concepts/key-terms/tracing/event-detail.mdx delete mode 100644 docs/platforms/javascript/legacy-sdk/integrations.mdx diff --git a/docs/concepts/key-terms/tracing/event-detail.mdx b/docs/concepts/key-terms/tracing/event-detail.mdx deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/docs/platforms/javascript/legacy-sdk/integrations.mdx b/docs/platforms/javascript/legacy-sdk/integrations.mdx deleted file mode 100644 index fbd1f60bb07ad..0000000000000 --- a/docs/platforms/javascript/legacy-sdk/integrations.mdx +++ /dev/null @@ -1,696 +0,0 @@ ---- -draft: true -categories: [] -toc: true -title: "Integrations (Legacy SDK)" -sidebar_title: "Integrations" -noindex: true -tags: [] ---- - - - -A new JavaScript SDK has superseded this deprecated version. Sentry preserves this documentation for customers using the old client. We recommend using the [updated JavaScript SDK](/platforms/javascript/) for new projects. - - - -Integrations extend the functionality of Raven.js to cover common libraries and environments automatically using simple plugins. - -## What are plugins? - -In Raven.js, plugins are little snippets of code to augment functionality for a -specific application/framework. We highly recommended you check out the list -of plugins and use what apply to your project. - -In order to keep the core small, we have opted to only include the most basic functionality by default, and you can pick and choose which plugins are applicable for you. - -## Why are plugins needed at all? - -JavaScript is pretty restrictive when it comes to exception handling, and there are a lot of things that make it difficult to get relevant information, so it’s important that we inject code and wrap things magically so we can extract what we need. See [_Usage_](/platforms/javascript/legacy-sdk/usage/) for tips regarding that. - -## Installation - -To install a plugin just include the plugin **after** Raven has been loaded and the Raven global variable is registered. This happens automatically if you install from a CDN with the required plugins in the URL. - -## AngularJS - -To use Sentry with your AngularJS (1.x) application, you will need to use both Raven.js (Sentry’s browser JavaScript SDK) and the Raven.js AngularJS plugin. - -On its own, Raven.js will report any uncaught exceptions triggered from your application. For advanced usage examples of Raven.js, please read [_Raven.js usage_](/platforms/javascript/legacy-sdk/usage/). - -Additionally, the Raven.js AngularJS plugin will catch any AngularJS-specific exceptions reported through AngularJS’s `$exceptionHandler` interface. - - - -This documentation is for Angular 1.x. See also: [_Angular 2.x_](#angular). - - - -{/* */} - -### Installation - -Raven.js and the Raven.js Angular plugin are distributed using a few different methods. - -#### Using our CDN - -For convenience, our CDN serves a single, minified JavaScript file containing both Raven.js and the Raven.js AngularJS plugin. It should be included **after** Angular, but **before** your application code. - -Example: - - -```html - - - -``` - -Note that this CDN build auto-initializes the Angular plugin. - -#### Using package managers - -Pre-built distributions of Raven.js and the Raven.js AngularJS plugin are available via both Bower and npm. - -##### Bower - - -```shell -bower install raven-js --save -``` - -```html - - - - -``` - -##### npm - - -```shell -npm install raven-js --save -``` - -```html - - - - -``` - -These examples assume that AngularJS is exported globally as _window.angular_. You can alternatively pass a reference to the _angular_ object directly as the second argument to _addPlugin_: - -```javascript -Raven.addPlugin(Raven.Plugins.Angular, angular); -``` - -#### Module loaders (CommonJS) - -Raven and the Raven AngularJS plugin can be loaded using a module loader like Browserify or webpack. - - -```javascript -var angular = require("angular"); -var Raven = require("raven-js"); - -Raven.config("___PUBLIC_DSN___") - .addPlugin(require("raven-js/plugins/angular"), angular) - .install(); -``` - -Note that when using CommonJS-style imports, you must pass a reference to _angular_ as the second argument to _addPlugin_. - -### AngularJS Configuration - -Inside your main AngularJS application module, you need to declare _ngRaven_ as a module dependency: - -```javascript -var myApp = angular.module("myApp", [ - "ngRaven", - "ngRoute", - "myAppControllers", - "myAppFilters", -]); -``` - -#### Module loaders (CommonJS) - -The raven angular module can be loaded using a module loader like Browserify or webpack. - -```javascript -var angular = require("angular"); -var ngRaven = require("raven-js/plugins/angular").moduleName; -var ngRoute = require("angular-route"); - -var myAppFilters = require("./myAppFilters"); -var myAppControllers = require("./myAppControllers"); -var moduleName = "myApp"; - -angular.module(moduleName, [ngRaven, ngRoute, myAppControllers, myAppFilters]); - -module.exports = moduleName; -``` - -{/* */} - -## Angular - -This document uses Angular to refer to Angular 2+. On its own, Raven.js will report any uncaught exceptions triggered from your application. For advanced usage examples of Raven.js, please read [_Raven.js usage_](/platforms/javascript/legacy-sdk/usage/). - -Additionally, Raven.js can be configured to catch any Angular-specific (2.x) exceptions reported through the [@angular/core/ErrorHandler](https://angular.io/api/core/ErrorHandler) component. - -### TypeScript Support - -Raven.js ships with a [TypeScript declaration file](https://www.npmjs.com/package/@types/raven-js), which helps static checking correctness of Raven.js API calls, and facilitates auto-complete in TypeScript-aware IDEs like Visual Studio Code. - -{/* */} - -### Installation - -Raven.js should be installed via npm. - -```shell -npm install raven-js --save -``` - -### Configuration - -Configuration depends on which module loader/packager you are using to build your Angular application. - -Below are instructions for [SystemJS](https://github.com/systemjs/systemjs), followed by instructions for [webpack](https://webpack.github.io/), Angular CLI, and other module loaders/packagers. - -#### SystemJS - -First, configure SystemJS to locate the Raven.js package: - -```javascript -System.config({ - packages: { - /* ... existing packages above ... */ - "raven-js": { - main: "dist/raven.js", - }, - }, - paths: { - /* ... existing paths above ... */ - "raven-js": "node_modules/raven-js", - }, -}); -``` - -Then, in your main module file (where `@NgModule` is called, e.g. app.module.ts): - - -```javascript -import Raven = require('raven-js'); -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule, ErrorHandler } from '@angular/core'; -import { AppComponent } from './app.component'; -import { environment } from '../environments/environment'; - -Raven - .config('___PUBLIC_DSN___') - .install(); - -export class RavenErrorHandler implements ErrorHandler { - handleError(err:any) : void { - Raven.captureException(err.originalError || err); - if(!environment.production) { - super.handleError(err); - } - } -} - -@NgModule({ - imports: [ BrowserModule ], - declarations: [ AppComponent ], - bootstrap: [ AppComponent ], - providers: [ { provide: ErrorHandler, useClass: RavenErrorHandler } ] -}) -export class AppModule { } -``` - -Once you’ve completed these two steps, you are done. - -#### Angular CLI - -Angular CLI now uses webpack to build instead of SystemJS. All you need to do is modify your main module file (where `@NgModule` is called, e.g. app.module.ts): - - -```javascript -import * as Raven from "raven-js"; -import { BrowserModule } from "@angular/platform-browser"; -import { NgModule, ErrorHandler } from "@angular/core"; -import { AppComponent } from "./app.component"; - -Raven.config("___PUBLIC_DSN___").install(); - -export class RavenErrorHandler implements ErrorHandler { - handleError(err: any): void { - Raven.captureException(err); - } -} - -@NgModule({ - imports: [BrowserModule], - declarations: [AppComponent], - bootstrap: [AppComponent], - providers: [{ provide: ErrorHandler, useClass: RavenErrorHandler }], -}) -export class AppModule {} -``` - -Once you’ve completed that step, you are done. - -{/* */} - -## Backbone - -{/* */} - -### Installation - -Start by adding the `raven.js` script tag to your page. It should be loaded as early as possible. - -```html - -``` - -### Configuring the Client - -Next configure Raven.js to use your Sentry DSN: - - -```javascript -Raven.config("___PUBLIC_DSN___").install(); -``` - -At this point, Raven is ready to capture any uncaught exception. - -{/* */} - -## Ember - -To use Sentry with your Ember application, you will need to use both Raven.js (Sentry’s browser JavaScript SDK) and the Raven.js Ember plugin. - -On its own, Raven.js will report any uncaught exceptions triggered from your application. For advanced usage examples of Raven.js, please read [_Raven.js usage_](/platforms/javascript/legacy-sdk/usage/). - -Additionally, the Raven.js Ember plugin will catch any Ember-specific exceptions reported through Ember’s [onerror](https://guides.emberjs.com/v3.2.0/configuring-ember/debugging/#toc_implement-an-ember-onerror-hook-to-log-all-errors-in-production). hook and any [RSVP promises](https://guides.emberjs.com/v3.2.0/configuring-ember/debugging/#toc_errors-within-an-code-rsvp-promise-code) that would otherwise be swallowed. - -{/* */} - -### Installation - -Raven.js and the Raven.js Ember plugin are distributed using a few different methods. - -#### Using our CDN - -For convenience, our CDN serves a single, minified JavaScript file containing both Raven.js and the Raven.js Ember plugin. It should be included **after** Ember, but **before** your application code. - -Example: - - -```html - - - -``` - -Note that this CDN build auto-initializes the Ember plugin. - -### Using package managers - -Pre-built distributions of Raven.js and the Raven.js Ember plugin are available via both Bower and npm for importing in your `ember-cli-build.js` file. - -##### Bower - - -```shell -bower install raven-js --save -``` - -```javascript -app.import("bower_components/raven-js/dist/raven.js"); -app.import("bower_components/raven-js/dist/plugins/ember.js"); -``` - -```html - - - -``` - -##### npm - - -```shell -npm install raven-js --save -``` - -```javascript -app.import("node_modules/raven-js/dist/raven.js"); -app.import("node_modules/raven-js/dist/plugins/ember.js"); -``` - -```html - - - -``` - -These examples assume that Ember is exported globally as `window.Ember`. You can alternatively pass a reference to the `Ember` object directly as the second argument to `addPlugin`: - -```javascript -Raven.addPlugin(Raven.Plugins.Ember, Ember); -``` - -## React - -{/* */} - -### Installation - -Start by adding the `raven.js` script tag to your page. It should be loaded as early as possible, before your main JavaScript bundle. - -```html - -``` - -### Configuring the Client - -Next configure Raven.js to use your Sentry DSN: - - -```javascript -Raven.config("___PUBLIC_DSN___").install(); -``` - -At this point, Raven is ready to capture any uncaught exception. - -### Expanded Usage - -If you’re using React 16 or above, [Error Boundaries](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) are an important tool for defining the behavior of your application in the face of errors. Be sure to send errors they catch to Sentry using `Raven.captureException`, and optionally this is also a great opportunity to surface [User Feedback](/platforms/javascript/user-feedback/) - -```javascript -class ExampleBoundary extends Component { - constructor(props) { - super(props); - this.state = { error: null }; - } - - componentDidCatch(error, errorInfo) { - this.setState({ error }); - Raven.captureException(error, { extra: errorInfo }); - } - - render() { - if (this.state.error) { - //render fallback UI - return ( -
Raven.lastEventId() && Raven.showReportDialog()} - > - -

We're sorry — something's gone wrong.

-

Our team has been notified, but click here fill out a report.

-
- ); - } else { - //when there's not an error, render children untouched - return this.props.children; - } - } -} -``` - -```html -
- -

Sidebar

- -
-

This content won't unmount when Widget throws.

-
-``` - -One important thing to note about the behavior of error boundaries in development mode is that React will rethrow errors they catch. This will result in errors being reported twice to Sentry with the above setup, but this won’t occur in your production build. - -Read more about error boundaries [in this blog post](https://blog.sentry.io/react-16-error-boundaries/). - -### Redux - -If you use [Redux](https://github.com/reactjs/redux) there are some useful community maintained middleware packages for annotating error reports with useful information, such as store state and recent actions: - -- [captbaritone/raven-for-redux](https://github.com/captbaritone/raven-for-redux) -- [ngokevin/redux-raven-middleware](https://github.com/ngokevin/redux-raven-middleware) - -### Redux Sagas Middleware - -If you’re using [Redux Saga](https://github.com/redux-saga/redux-saga) be aware that it does not bubble errors up to the browsers uncaught exception handler. - -You may specify an error handler that captures saga exceptions by passing an `onError` function to the `createSagaMiddleware` options, and call `Raven.captureException` inside that callback. See the [Redux Saga documentation](https://redux-saga.js.org/docs/api/#createsagamiddlewareoptions) for more details. - -## Vue.js (2.0) - - - -This plugin only works with Vue 2.0 or greater. - - - -To use Sentry with your Vue application, you will need to use both Raven.js (Sentry’s browser JavaScript SDK) and the Raven.js Vue plugin. - -On its own, Raven.js will report any uncaught exceptions triggered from your application. For advanced usage examples of Raven.js, please read [_Raven.js usage_](/platforms/javascript/legacy-sdk/usage/). - -Additionally, the Raven.js Vue plugin will capture the name and props state of the active component where the error was thrown. This is reported via Vue’s _config.errorHandler_ hook. - -{/* */} - -### Installation - -Raven.js and the Raven.js Vue plugin are distributed using a few different methods. - -#### Using our CDN - -For convenience, our CDN serves a single, minified JavaScript file containing both Raven.js and the Raven.js Vue plugin. It should be included **after** Vue, but **before** your application code. - -Example: - - -```html - - - -``` - -Note that this CDN build auto-initializes the Vue plugin. - -#### Using package managers - -Both Raven.js and the Raven.js Vue plugin can be installed via npm and Bower. - -##### npm - - -```shell -npm install raven-js --save -``` - -```html - - - - -``` - -##### Bower - - -```shell -bower install raven-js --save -``` - -```html - - - - -``` - -These examples assume that Vue is exported globally as _window.Vue_. You can alternatively pass a reference to the _Vue_ object directly as the second argument to _addPlugin_: - -```javascript -Raven.addPlugin(Raven.Plugins.Vue, Vue); -``` - -#### Module loaders - -In your main application file, import and configure both Raven.js and the Raven.js Vue plugin as follows: - - -```javascript -import Vue from "vue"; -import Raven from "raven-js"; -import RavenVue from "raven-js/plugins/vue"; - -Raven.config("___PUBLIC_DSN___").addPlugin(RavenVue, Vue).install(); -``` - -## Deprecated React Native - -The deprecated plugin “React Native for Raven.js” is a pure JavaScript error reporting solution. The plugin will report errors originating from React Native’s JavaScript engine (e.g. programming errors like “x is undefined”), but might not catch errors that originate from the underlying operating system (iOS / Android) unless they happen to be transmitted to React Native’s global error handler. - -**Do not use this plugin for new code but instead use the new :ref:`react-native` client integration instead**. - - - -Unless you have specific reasons not to, it’s recommended to instead the new [React Native](platforms/react-native/) client instead which supports native and JavaScript crashes as well as an improved integration into the Xcode build process. - - - -### Installation - -In the root of your React Native project, install raven-js via npm: - -```bash -npm install raven-js --save -``` - -At the top of your main application file (e.g. index.ios.js and/or index.android.js), add the following code: - -```javascript -var React = require("react"); - -var Raven = require("raven-js"); -require("raven-js/plugins/react-native")(Raven); -``` - -### Configuring the Client - -Now we need to set up Raven.js to use your Sentry DSN: - - -```javascript -Raven.config("___PUBLIC_DSN___", { release: RELEASE_ID }).install(); -``` - -RELEASE_ID is a string representing the “version” of the build you are about to distribute. This can be the SHA of your Git repository’s HEAD. It can also be a semantic version number (e.g. “1.1.2”), pulled from your project’s package.json file. More below. - -### About Releases - -Every time you build and distribute a new version of your React Native app, you’ll want to create a new release inside Sentry. This is for two important reasons: - -- You can associate errors tracked by Sentry with a particular build -- You can store your source files/source maps generated for each build inside Sentry - -Unlike a normal web application where your JavaScript files (and source maps) are served and hosted from a web server, your React Native code is being served from the target device’s filesystem. So you’ll need to upload both your **source code** AND **source maps** directly to Sentry, so that we can generate handy stack traces for you to browse when examining exceptions triggered by your application. - -### Generating and Uploading Source Files/Source Maps - -To generate both an application JavaScript file (main.jsbundle) and source map for your project (main.jsbundle.map), use the React Native CLI tool: - -```bash -react-native bundle \ - --dev false \ - --platform ios \ - --entry-file index.ios.js \ - --reset-cache \ - --bundle-output main.jsbundle \ - --sourcemap-output main.jsbundle.map -``` - -This will write both main.jsbundle and main.jsbundle.map to the current directory. Next, you’ll need to [create a new release and upload these files as release artifacts](/platforms/javascript/legacy-sdk/sourcemaps/). - -#### Naming your Artifacts - -In Sentry, artifacts are designed to be “named” using the full URL or path at which that artifact is located (e.g. _https://example.org/app.js_ or _/path/to/file.js/_). Since React Native applications are installed to a user’s device, on a path that includes unique device identifiers (and thus different for every user), the React Native plugin strips the entire path leading up to your application root. - -This means that although your code may live at the following path: - -``` -/var/containers/Bundle/Application/{DEVICE_ID}/HelloWorld.app/main.jsbundle -``` - -The React Native plugin will reduce this to: - -``` -/main.jsbundle -``` - -Therefore in this example, you should name your artifacts as “/main.jsbundle” and “/main.jsbundle.map”. - -### Source Maps with the Simulator - -When developing with the simulator, it is not necessary to build source maps manually, as they are generated automatically on-demand. - -Note however that artifact names are completely different when using the simulator. This is because instead of those files existing on a path on a device, they are served over HTTP via the [React Native packager](https://github.com/facebook/metro). - -Typically, simulator assets are served at the following URLs: - -- Bundle: [http://localhost:8081/index.ios.bundle?platform=ios&dev=true](http://localhost:8081/index.ios.bundle?platform=ios&dev=true) -- Source map: [http://localhost:8081/index.ios.map?platform=ios&dev=true](http://localhost:8081/index.ios.map?platform=ios&dev=true) - -If you want to evaluate Sentry’s source map support using the simulator, you will need to fetch these assets at these URLs (while the React Native packager is running), and upload them to Sentry as artifacts. They should be named using the full URL at which they are located, including the query string. - -### Expanded Usage - -It’s likely you’ll end up in situations where you want to gracefully handle errors. A good pattern for this would be to setup a logError helper: - -```javascript -function logException(ex, context) { - Raven.captureException(ex, { - extra: context, - }); - /*eslint no-console:0*/ - window.console && console.error && console.error(ex); -} -``` - -Now in your components (or anywhere else), you can fail gracefully: - -```javascript -var Component = React.createClass({ - render() { - try { - // .. - } catch (ex) { - logException(ex); - } - }, -}); -``` From 0c85341279e28a2b3bff58e62562103a7f103d2b Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 14 Oct 2026 11:12:08 +0200 Subject: [PATCH 3/5] test(redirects): [POC A] Fail when a redirect hides an existing page Next.js checks redirects before it serves pages. A redirect source that matches an existing page makes the page unreachable, for example a page added under a guide that a catch-all rule sends to another guide. The test matches every page path against the sources in redirects.js with the same matcher as Next.js, and against middleware.ts. Co-Authored-By: Claude Opus 5.5 --- src/redirects.spec.ts | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/redirects.spec.ts diff --git a/src/redirects.spec.ts b/src/redirects.spec.ts new file mode 100644 index 0000000000000..8398e258e8233 --- /dev/null +++ b/src/redirects.spec.ts @@ -0,0 +1,52 @@ +import path from 'node:path'; + +import {modifyRouteRegex} from 'next/dist/lib/redirect-status'; +import {getPathMatch} from 'next/dist/shared/lib/router/utils/path-match'; +import {expect, test} from 'vitest'; + +import {userDocsRedirects} from '../redirects'; +import {parseMiddlewareTs} from '../scripts/check-redirects-on-rename'; +import {getDocsFrontMatterFromDirectory} from './frontmatter'; +import {isVersioned} from './versioning'; + +const root = path.join(__dirname, '..'); + +// Same matching as Next.js uses for the `redirects` in next.config.ts +const matchesRedirectSource = (source: string) => + getPathMatch(source, { + strict: true, + regexModifier: regex => modifyRouteRegex(regex, ['/_next']), + }); + +// Same matching as `handleRedirects` in middleware.ts +const guideRegex = /\/guides\/(\w+)\//g; +const matchesMiddlewareSource = (sources: Set) => (pagePath: string) => + sources.has(pagePath) || sources.has(pagePath.replace(guideRegex, '/guides/:guide/')); + +test('redirects do not hide existing pages', {timeout: 60_000}, async () => { + const pages = await getDocsFrontMatterFromDirectory(path.join(root, 'docs')); + const pagePaths = pages.map(({slug}) => + isVersioned(slug) ? `/${slug}` : `/${slug}/`.replace(/\/index\/$/, '/') + ); + + const middlewareSources = new Set( + parseMiddlewareTs(path.join(root, 'middleware.ts')).userDocsRedirects.map( + ({source}) => source + ) + ); + const matchers = [ + ...userDocsRedirects.map(({source}) => ({ + source: `redirects.js: ${source}`, + matches: matchesRedirectSource(source), + })), + {source: 'middleware.ts', matches: matchesMiddlewareSource(middlewareSources)}, + ]; + + const hiddenPages = pagePaths.flatMap(pagePath => + matchers + .filter(({matches}) => matches(pagePath)) + .map(({source}) => `${pagePath} (${source})`) + ); + + expect(hiddenPages).toEqual([]); +}); From 92a5f8a266640c0ce96566c621d0b619a86ec7bd Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 14 Oct 2026 14:21:46 +0200 Subject: [PATCH 4/5] feat(js): [POC Selector 2+3] Add the framework selector to runtime quick starts Port of the framework selector from the fn/node-framework-selector experiment. The Node.js, Bun and Deno quick starts get a dropdown that swaps in the Express, Fastify, Koa or Hapi snippets, loaded on demand from a static API route. The landing pages link to the quick starts with the framework selected. Cloudflare is left out, because these frameworks have no tested setup on Workers. Co-Authored-By: Claude Opus 5.5 --- .../[framework]/[...includePath]/route.ts | 140 ++++++++ .../platforms/javascript/guides/bun/index.mdx | 65 +--- .../javascript/guides/deno/index.mdx | 65 +--- .../javascript/guides/express/index.mdx | 6 +- .../javascript/guides/fastify/index.mdx | 6 +- .../javascript/guides/hapi/index.mdx | 6 +- .../platforms/javascript/guides/koa/index.mdx | 6 +- .../javascript/guides/node/index.mdx | 4 + .../javascript.fastify.mdx | 2 +- .../getting-started-node/javascript.mdx | 14 +- .../javascript.express.mdx | 8 - .../javascript.fastify.mdx | 8 - .../javascript.hapi.mdx | 8 - .../javascript.koa.mdx | 8 - .../javascript.mdx | 60 ++++ .../javascript.node.mdx | 61 +--- .../frameworkSelector/frameworks.ts | 69 ++++ src/components/frameworkSelector/index.tsx | 332 ++++++++++++++++++ src/components/migrationGuide/index.tsx | 7 +- src/components/platformContent.tsx | 134 +------ src/mdxComponents.ts | 2 + src/platformContentResolver.ts | 137 ++++++++ 22 files changed, 799 insertions(+), 349 deletions(-) create mode 100644 app/api/platform-content/[platform]/[runtime]/[framework]/[...includePath]/route.ts rename platform-includes/{getting-started-verify => getting-started-verify-issues-tracing}/javascript.express.mdx (85%) rename platform-includes/{getting-started-verify => getting-started-verify-issues-tracing}/javascript.fastify.mdx (85%) rename platform-includes/{getting-started-verify => getting-started-verify-issues-tracing}/javascript.hapi.mdx (86%) rename platform-includes/{getting-started-verify => getting-started-verify-issues-tracing}/javascript.koa.mdx (85%) create mode 100644 platform-includes/getting-started-verify-issues-tracing/javascript.mdx create mode 100644 src/components/frameworkSelector/frameworks.ts create mode 100644 src/components/frameworkSelector/index.tsx create mode 100644 src/platformContentResolver.ts diff --git a/app/api/platform-content/[platform]/[runtime]/[framework]/[...includePath]/route.ts b/app/api/platform-content/[platform]/[runtime]/[framework]/[...includePath]/route.ts new file mode 100644 index 0000000000000..0d6ccee329fa8 --- /dev/null +++ b/app/api/platform-content/[platform]/[runtime]/[framework]/[...includePath]/route.ts @@ -0,0 +1,140 @@ +import { + PlatformContentPayload, + RUNTIMES, +} from 'sentry-docs/components/frameworkSelector/frameworks'; +import {getFileBySlugWithCache} from 'sentry-docs/mdx'; +import {resolvePlatformContent} from 'sentry-docs/platformContentResolver'; + +/** + * Serves the compiled MDX of a `` include as it applies to a framework running + * on a runtime, so a runtime's page can swap in framework snippets client-side + * (see ``). + * + * Only the combinations from `generateStaticParams` are built; there is no runtime rendering. + */ + +export const dynamic = 'force-static'; +export const dynamicParams = false; + +export function generateStaticParams() { + return Object.entries(RUNTIMES).flatMap(([runtime, {frameworks, includePaths}]) => + frameworks.flatMap(({key: framework}) => + includePaths.map(includePath => ({ + platform: 'javascript', + runtime, + framework, + includePath: includePath.split('/'), + })) + ) + ); +} + +type SlugFile = Awaited>; + +async function tryGetFile(slug: string): Promise { + try { + return await getFileBySlugWithCache(slug); + } catch { + return undefined; + } +} + +/** + * Resolves an include for a framework on a runtime: + * 1. `..`, for snippets specific to both + * 2. On Node.js, the framework guide's content, which falls back to Node.js + * Elsewhere, only a `.` snippet, to not pull in Node.js content + * 3. The runtime guide's own content + */ +async function resolveForFramework( + includePath: string, + platform: string, + runtime: string, + framework: string +): Promise { + const base = `platform-includes/${includePath}/${platform}`; + + const doc = await tryGetFile(`${base}.${runtime}.${framework}`); + if (doc) { + return doc; + } + + if (runtime === 'node') { + return resolvePlatformContent({ + includePath, + path: ['platforms', platform, 'guides', framework], + }); + } + + return ( + (await tryGetFile(`${base}.${framework}`)) ?? + resolvePlatformContent({ + includePath, + path: ['platforms', platform, 'guides', runtime], + }) + ); +} + +const INCLUDE_RE = / Promise, + payload: PlatformContentPayload +) { + for (const [, rawName] of source.matchAll(INCLUDE_RE)) { + const name = rawName.replace(/\.mdx$/, ''); + if (name in payload.includes) { + continue; + } + // Missing includes render nothing, same as . + const doc = await tryGetFile(`includes/${name}`); + if (doc) { + payload.includes[name] = doc.mdxSource; + await collectNested(doc.matter.content, resolve, payload); + } + } + + for (const [, includePath] of source.matchAll(PLATFORM_CONTENT_RE)) { + if (includePath in payload.platformContents) { + continue; + } + const doc = await resolve(includePath); + payload.platformContents[includePath] = doc?.mdxSource ?? null; + if (doc) { + await collectNested(doc.matter.content, resolve, payload); + } + } +} + +export async function GET( + _request: Request, + { + params, + }: { + params: Promise<{ + framework: string; + includePath: string[]; + platform: string; + runtime: string; + }>; + } +) { + const {platform, runtime, framework, includePath} = await params; + const resolve = (path: string) => + resolveForFramework(path, platform, runtime, framework); + + const doc = await resolve(includePath.join('/')); + + const payload: PlatformContentPayload = { + code: doc?.mdxSource ?? null, + includes: {}, + platformContents: {}, + }; + if (doc) { + await collectNested(doc.matter.content, resolve, payload); + } + + return Response.json(payload); +} diff --git a/docs/platforms/javascript/guides/bun/index.mdx b/docs/platforms/javascript/guides/bun/index.mdx index d70ce4d8aa9ac..735fa5f6ff5b0 100644 --- a/docs/platforms/javascript/guides/bun/index.mdx +++ b/docs/platforms/javascript/guides/bun/index.mdx @@ -7,6 +7,8 @@ categories: - server-only --- + + @@ -140,66 +142,7 @@ bun --preload ./instrument.js dist/app.js Let's test your setup and confirm that Sentry is working correctly and sending data to your Sentry project. -### Issues - - - - - -First, let's verify that Sentry captures errors and creates issues in your Sentry project. Add the following code snippet to your main application file, which will call an undefined function, triggering an error that Sentry will capture: - - - - -```javascript -setTimeout(() => { - try { - foo(); - } catch (e) { - Sentry.captureException(e); - } -}, 99); -``` - - - - - - -### Tracing - - - - - -To test your tracing configuration, update the previous code snippet by starting a trace to measure the time it takes for the execution of your code: - - - - -```javascript -Sentry.startSpan( - { - op: "test", - name: "My First Test Transaction", - }, - () => { - setTimeout(() => { - try { - foo(); - } catch (e) { - Sentry.captureException(e); - } - }, 99); - } -); -``` - - - - - - + @@ -236,3 +179,5 @@ Our next recommended steps for you are: + + diff --git a/docs/platforms/javascript/guides/deno/index.mdx b/docs/platforms/javascript/guides/deno/index.mdx index 03e6221cbfa32..63ecb457e0af3 100644 --- a/docs/platforms/javascript/guides/deno/index.mdx +++ b/docs/platforms/javascript/guides/deno/index.mdx @@ -7,6 +7,8 @@ categories: - server-only --- + + This SDK is currently in **beta**. Beta features are still in progress and may have bugs. Please reach out on [GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback or concerns @@ -156,66 +158,7 @@ If you omit `--allow-read` or `--allow-sys`, the SDK sends events without that d Let's test your setup and confirm that Sentry is working correctly and sending data to your Sentry project. -### Issues - - - - - -First, let's verify that Sentry captures errors and creates issues in your Sentry project. Add the following code snippet to your main application file, which will call an undefined function, triggering an error that Sentry will capture: - - - - -```javascript -setTimeout(() => { - try { - foo(); - } catch (e) { - Sentry.captureException(e); - } -}, 99); -``` - - - - - - -### Tracing - - - - - -To test your tracing configuration, update the previous code snippet by starting a trace to measure the time it takes for the execution of your code: - - - - -```javascript -Sentry.startSpan( - { - op: "test", - name: "My First Test Transaction", - }, - () => { - setTimeout(() => { - try { - foo(); - } catch (e) { - Sentry.captureException(e); - } - }, 99); - } -); -``` - - - - - - + @@ -251,3 +194,5 @@ Now's a good time to customize your setup and look into more advanced topics. Ou + + diff --git a/docs/platforms/javascript/guides/express/index.mdx b/docs/platforms/javascript/guides/express/index.mdx index d7cdeba89b349..222756abf18f0 100644 --- a/docs/platforms/javascript/guides/express/index.mdx +++ b/docs/platforms/javascript/guides/express/index.mdx @@ -16,17 +16,17 @@ categories: - - - ## Customize Error Capturing diff --git a/docs/platforms/javascript/guides/fastify/index.mdx b/docs/platforms/javascript/guides/fastify/index.mdx index 3159223afd40d..2e8735557adb6 100644 --- a/docs/platforms/javascript/guides/fastify/index.mdx +++ b/docs/platforms/javascript/guides/fastify/index.mdx @@ -16,17 +16,17 @@ categories: - - - ## Customize Error Capturing diff --git a/docs/platforms/javascript/guides/hapi/index.mdx b/docs/platforms/javascript/guides/hapi/index.mdx index aa00c6ae39366..147b858df193c 100644 --- a/docs/platforms/javascript/guides/hapi/index.mdx +++ b/docs/platforms/javascript/guides/hapi/index.mdx @@ -16,17 +16,17 @@ categories: - - - To configure the integration, see the [Hapi integration](/platforms/javascript/guides/node/configuration/integrations/hapi/). diff --git a/docs/platforms/javascript/guides/koa/index.mdx b/docs/platforms/javascript/guides/koa/index.mdx index 46c24b474fa70..33d7cff36ebad 100644 --- a/docs/platforms/javascript/guides/koa/index.mdx +++ b/docs/platforms/javascript/guides/koa/index.mdx @@ -16,17 +16,17 @@ categories: - - - To configure the integration, see the [Koa integration](/platforms/javascript/guides/node/configuration/integrations/koa/). diff --git a/docs/platforms/javascript/guides/node/index.mdx b/docs/platforms/javascript/guides/node/index.mdx index 69db24d7d47bf..3fc185556448d 100644 --- a/docs/platforms/javascript/guides/node/index.mdx +++ b/docs/platforms/javascript/guides/node/index.mdx @@ -7,8 +7,12 @@ categories: - server-only --- + + + + diff --git a/platform-includes/getting-started-capture-errors/javascript.fastify.mdx b/platform-includes/getting-started-capture-errors/javascript.fastify.mdx index f8b66c63d354f..0b495323ae387 100644 --- a/platform-includes/getting-started-capture-errors/javascript.fastify.mdx +++ b/platform-includes/getting-started-capture-errors/javascript.fastify.mdx @@ -1,3 +1,3 @@ ### Report Unhandled Exceptions -`fastifyIntegration` captures errors from your routes and hooks automatically, so you don't need to register a Sentry error handler. Errors with a 3xx or 4xx status code are skipped. To change which errors are captured, use the `shouldHandleError` option. +`fastifyIntegration` captures errors from your routes and hooks automatically, so you don't need to register a Sentry error handler. Errors with a 3xx or 4xx status code are skipped. To change which errors are captured, use the [`shouldHandleError`](/platforms/javascript/guides/node/configuration/integrations/fastify/#shouldhandleerror) option. diff --git a/platform-includes/getting-started-node/javascript.mdx b/platform-includes/getting-started-node/javascript.mdx index bdfbdd4b138e1..4984c07c7c5eb 100644 --- a/platform-includes/getting-started-node/javascript.mdx +++ b/platform-includes/getting-started-node/javascript.mdx @@ -1,4 +1,4 @@ - + @@ -23,7 +23,7 @@ Run the command for your preferred package manager to add the Sentry SDK to your - + @@ -35,7 +35,7 @@ Run the command for your preferred package manager to add the Sentry SDK to your To import and initialize Sentry, create a file named `instrument.(js|mjs)` in the root directory of your project and add the following code: - + Then, use the [--import](https://nodejs.org/api/cli.html#--importmodule) command line option and point it to your `instrument.(js|mjs)` file to load the module before the application starts: @@ -49,21 +49,21 @@ node --import ./instrument.mjs app.mjs If you are using a bundler like vite, you can also follow the bundler setup docs instead. - + ### Add Readable Stack Traces With Source Maps (Optional) - + ### Control the Data You Send to Sentry (Optional) - + ## Verify Your Setup Let's test your setup and confirm that Sentry is working correctly and sending data to your Sentry project. - + ### View Captured Data in Sentry diff --git a/platform-includes/getting-started-verify/javascript.express.mdx b/platform-includes/getting-started-verify-issues-tracing/javascript.express.mdx similarity index 85% rename from platform-includes/getting-started-verify/javascript.express.mdx rename to platform-includes/getting-started-verify-issues-tracing/javascript.express.mdx index 3cb0b89c252f5..3849da6497c7a 100644 --- a/platform-includes/getting-started-verify/javascript.express.mdx +++ b/platform-includes/getting-started-verify-issues-tracing/javascript.express.mdx @@ -52,11 +52,3 @@ app.get("/debug-sentry", async (req, res) => { - - - - - - - - diff --git a/platform-includes/getting-started-verify/javascript.fastify.mdx b/platform-includes/getting-started-verify-issues-tracing/javascript.fastify.mdx similarity index 85% rename from platform-includes/getting-started-verify/javascript.fastify.mdx rename to platform-includes/getting-started-verify-issues-tracing/javascript.fastify.mdx index 116530e770384..3588502fe5abf 100644 --- a/platform-includes/getting-started-verify/javascript.fastify.mdx +++ b/platform-includes/getting-started-verify-issues-tracing/javascript.fastify.mdx @@ -52,11 +52,3 @@ app.get("/debug-sentry", async (request, reply) => { - - - - - - - - diff --git a/platform-includes/getting-started-verify/javascript.hapi.mdx b/platform-includes/getting-started-verify-issues-tracing/javascript.hapi.mdx similarity index 86% rename from platform-includes/getting-started-verify/javascript.hapi.mdx rename to platform-includes/getting-started-verify-issues-tracing/javascript.hapi.mdx index 183d9c7798e5a..c1c7965617799 100644 --- a/platform-includes/getting-started-verify/javascript.hapi.mdx +++ b/platform-includes/getting-started-verify-issues-tracing/javascript.hapi.mdx @@ -60,11 +60,3 @@ server.route({ - - - - - - - - diff --git a/platform-includes/getting-started-verify/javascript.koa.mdx b/platform-includes/getting-started-verify-issues-tracing/javascript.koa.mdx similarity index 85% rename from platform-includes/getting-started-verify/javascript.koa.mdx rename to platform-includes/getting-started-verify-issues-tracing/javascript.koa.mdx index 51a7a87773429..abb7448052fb8 100644 --- a/platform-includes/getting-started-verify/javascript.koa.mdx +++ b/platform-includes/getting-started-verify-issues-tracing/javascript.koa.mdx @@ -59,11 +59,3 @@ app.use(async (ctx, next) => { - - - - - - - - diff --git a/platform-includes/getting-started-verify-issues-tracing/javascript.mdx b/platform-includes/getting-started-verify-issues-tracing/javascript.mdx new file mode 100644 index 0000000000000..190411a3c6a4a --- /dev/null +++ b/platform-includes/getting-started-verify-issues-tracing/javascript.mdx @@ -0,0 +1,60 @@ +### Issues + + + + + +First, let's verify that Sentry captures errors and creates issues in your Sentry project. Add the following code snippet to your main application file, which will call an undefined function, triggering an error that Sentry will capture: + + + + +```javascript +setTimeout(() => { + try { + foo(); + } catch (e) { + Sentry.captureException(e); + } +}, 99); +``` + + + + + + +### Tracing + + + + + +To test your tracing configuration, update the previous code snippet by starting a trace to measure the time it takes for the execution of your code: + + + + +```javascript +Sentry.startSpan( + { + op: "test", + name: "My First Test Transaction", + }, + () => { + setTimeout(() => { + try { + foo(); + } catch (e) { + Sentry.captureException(e); + } + }, 99); + } +); +``` + + + + + + diff --git a/platform-includes/getting-started-verify/javascript.node.mdx b/platform-includes/getting-started-verify/javascript.node.mdx index 8190b444daf1e..a04d9d351be05 100644 --- a/platform-includes/getting-started-verify/javascript.node.mdx +++ b/platform-includes/getting-started-verify/javascript.node.mdx @@ -1,63 +1,4 @@ -### Issues - - - - - -First, let's verify that Sentry captures errors and creates issues in your Sentry project. Add the following code snippet to your main application file, which will call an undefined function, triggering an error that Sentry will capture: - - - - -```javascript -setTimeout(() => { - try { - foo(); - } catch (e) { - Sentry.captureException(e); - } -}, 99); -``` - - - - - - -### Tracing - - - - - -To test your tracing configuration, update the previous code snippet by starting a performance trace to measure the time it takes for the execution of your code: - - - - -```javascript -Sentry.startSpan( - { - op: "test", - name: "My First Test Transaction", - }, - () => { - setTimeout(() => { - try { - foo(); - } catch (e) { - Sentry.captureException(e); - } - }, 99); - } -); -``` - - - - - - + diff --git a/src/components/frameworkSelector/frameworks.ts b/src/components/frameworkSelector/frameworks.ts new file mode 100644 index 0000000000000..7898b6d91870a --- /dev/null +++ b/src/components/frameworkSelector/frameworks.ts @@ -0,0 +1,69 @@ +export type Framework = { + /** Guide slug, e.g. `express` for `/platforms/javascript/guides/express/`. */ + key: string; + title: string; +}; + +const EXPRESS: Framework = {key: 'express', title: 'Express'}; +const FASTIFY: Framework = {key: 'fastify', title: 'Fastify'}; +const KOA: Framework = {key: 'koa', title: 'Koa'}; +const HAPI: Framework = {key: 'hapi', title: 'Hapi'}; + +export type Runtime = { + /** Frameworks selectable on the runtime's getting started page. */ + frameworks: Framework[]; + /** + * `` include paths on the runtime's getting started page. + * Each one is pre-built per framework as a static JSON file that the page loads on demand. + */ + includePaths: string[]; + title: string; +}; + +/** Runtime guides (keyed by guide slug) that have a framework selector on their getting started page. */ +export const RUNTIMES: Record = { + node: { + title: 'Node.js', + frameworks: [EXPRESS, FASTIFY, KOA, HAPI], + includePaths: [ + 'getting-started-prerequisites', + 'getting-started-install', + 'getting-started-config', + 'getting-started-capture-errors', + 'getting-started-sourcemaps-short-version', + 'getting-started-data-collection', + 'getting-started-verify', + ], + }, + bun: { + title: 'Bun', + frameworks: [EXPRESS, FASTIFY, KOA, HAPI], + includePaths: ['getting-started-verify-issues-tracing'], + }, + deno: { + title: 'Deno', + frameworks: [EXPRESS, FASTIFY, KOA, HAPI], + includePaths: ['getting-started-verify-issues-tracing'], + }, +}; + +export const FRAMEWORK_QUERY_PARAM = 'framework'; + +/** The serialized form of a `` include, served by the platform-content API route. */ +export type PlatformContentPayload = { + /** Compiled MDX of the resolved include, or `null` if there is no content for it. */ + code: string | null; + /** Compiled MDX of nested `` files, keyed by name. */ + includes: Record; + /** Compiled MDX of nested `` files, keyed by include path. */ + platformContents: Record; +}; + +export function platformContentUrl( + platform: string, + runtime: string, + framework: string, + includePath: string +) { + return `/api/platform-content/${platform}/${runtime}/${framework}/${includePath}/`; +} diff --git a/src/components/frameworkSelector/index.tsx b/src/components/frameworkSelector/index.tsx new file mode 100644 index 0000000000000..512475168cb89 --- /dev/null +++ b/src/components/frameworkSelector/index.tsx @@ -0,0 +1,332 @@ +'use client'; + +import {ChevronDownIcon} from '@radix-ui/react-icons'; +import * as RadixSelect from '@radix-ui/react-select'; +import {createContext, ReactNode, useContext, useEffect, useMemo, useState} from 'react'; +import {getMDXComponent} from 'sentry-docs/getMDXComponent'; + +import {Alert} from '../alert'; +import {CodeBlock} from '../codeBlock'; +import {CodeContext} from '../codeContext'; +import {CodeTabs} from '../codeTabs'; +import {Expandable} from '../expandable'; +import {FeatureBadge} from '../featureBadge'; +import { + OnboardingOption, + OnboardingOptionButtons, + OnboardingSteps, + updateElementsVisibilityForOptions, +} from '../onboarding'; +import {SmartLink} from '../smartLink'; +import { + SplitLayout, + SplitSection, + SplitSectionCode, + SplitSectionText, +} from '../splitLayout'; +import styles from '../versionSelector/style.module.scss'; +import { + Framework, + FRAMEWORK_QUERY_PARAM, + PlatformContentPayload, + platformContentUrl, + RUNTIMES, +} from './frameworks'; + +const DEFAULT_VALUE = 'none'; + +type FrameworkContextValue = { + framework: Framework | null; + platform: string; + /** The runtime guide the page belongs to, e.g. `node`. */ + runtime: string; +}; + +const FrameworkContext = createContext(null); + +function readFrameworkFromUrl(frameworks: Framework[]): Framework | null { + const key = new URLSearchParams(window.location.search).get(FRAMEWORK_QUERY_PARAM); + return frameworks.find(f => f.key === key) ?? null; +} + +function writeFrameworkToUrl(framework: Framework | null) { + const url = new URL(window.location.href); + if (framework) { + url.searchParams.set(FRAMEWORK_QUERY_PARAM, framework.key); + } else { + url.searchParams.delete(FRAMEWORK_QUERY_PARAM); + } + window.history.replaceState(window.history.state, '', url); +} + +/** + * Renders a framework dropdown and lets `` blocks among its + * children swap to the selected framework's snippets for `runtime`, loaded on demand. + * The selection is kept in the `?framework=` query param. + */ +export function FrameworkSelector({ + children, + runtime, + platform = 'javascript', +}: { + children: ReactNode; + runtime: string; + platform?: string; +}) { + const runtimeConfig = RUNTIMES[runtime]; + const frameworks = useMemo(() => runtimeConfig?.frameworks ?? [], [runtimeConfig]); + const [framework, setFramework] = useState(null); + + // The page is statically rendered, so the query param can only be read after mount. + useEffect(() => { + setFramework(readFrameworkFromUrl(frameworks)); + }, [frameworks]); + + const handleChange = (value: string) => { + const selected = frameworks.find(f => f.key === value) ?? null; + setFramework(selected); + writeFrameworkToUrl(selected); + }; + + const contextValue = useMemo( + () => ({framework, platform, runtime}), + [framework, platform, runtime] + ); + + return ( + +
+ +
+ + + + + + + + + + + None (plain {runtimeConfig?.title ?? runtime}) + + + {frameworks.map(f => ( + + {f.title} + + ))} + + +
+
+ {children} +
+ ); +} + +const payloadCache = new Map>(); + +function fetchPayload(url: string): Promise { + let promise = payloadCache.get(url); + if (!promise) { + promise = fetch(url).then(res => { + if (!res.ok) { + throw new Error(`Failed to load ${url}: ${res.status}`); + } + return res.json(); + }); + // Allow retrying after a failed request + promise.catch(() => payloadCache.delete(url)); + payloadCache.set(url, promise); + } + return promise; +} + +/** + * Client-side stand-ins for the server-only MDX components, scoped to the selected + * framework on the page's runtime. + */ +function clientMdxComponents( + payload: PlatformContentPayload, + platform: string, + runtime: string, + framework: string +) { + // Checked in order, like the server's fallback chain + const platformKeys = [`${platform}.${framework}`, `${platform}.${runtime}`]; + + function MDX({code}: {code: string | null | undefined}) { + const Component = useMemo(() => (code ? getMDXComponent(code) : null), [code]); + return Component ? : null; + } + + function Include({name}: {name: string}) { + return ; + } + + function PlatformContent({includePath}: {includePath: string}) { + return ; + } + + function PlatformLink({children, to}: {children: ReactNode; to?: string}) { + if (!to) { + return children; + } + return ( + + {children} + + ); + } + + function PlatformSection({ + children, + supported = [], + notSupported = [], + noGuides, + }: { + children: ReactNode; + noGuides?: boolean; + notSupported?: string[]; + supported?: string[]; + }) { + if (noGuides) { + return null; + } + for (const key of platformKeys) { + if (supported.includes(key)) { + return
{children}
; + } + if (notSupported.includes(key)) { + return null; + } + } + return supported.length ? null :
{children}
; + } + + const components = { + Alert, + CodeBlock, + CodeTabs, + Expandable, + FeatureBadge, + Include, + Link: SmartLink, + OnboardingOption, + OnboardingOptionButtons, + OnboardingSteps, + PlatformContent, + PlatformLink, + PlatformSection, + SplitLayout, + SplitSection, + SplitSectionCode, + SplitSectionText, + a: SmartLink, + }; + + return {MDX, components}; +} + +function RemoteFrameworkContent({ + fallback, + framework, + includePath, + platform, + runtime, +}: { + fallback: ReactNode; + framework: Framework; + includePath: string; + platform: string; + runtime: string; +}) { + const url = platformContentUrl(platform, runtime, framework.key, includePath); + const [payload, setPayload] = useState(null); + const [error, setError] = useState(false); + const codeContext = useContext(CodeContext); + + useEffect(() => { + let cancelled = false; + setPayload(null); + setError(false); + fetchPayload(url).then( + result => !cancelled && setPayload(result), + () => !cancelled && setError(true) + ); + return () => { + cancelled = true; + }; + }, [url]); + + const mdx = useMemo( + () => + payload ? clientMdxComponents(payload, platform, runtime, framework.key) : null, + [payload, platform, runtime, framework.key] + ); + + // Apply the current feature selection (tracing, profiling, ...) to the new content + useEffect(() => { + if (payload && codeContext?.onboardingOptions) { + updateElementsVisibilityForOptions(codeContext.onboardingOptions, false); + } + }, [payload, codeContext?.onboardingOptions]); + + if (error) { + return fallback; + } + + if (!payload || !mdx) { + return ( +
+ {fallback} +
+ ); + } + + return ; +} + +/** + * Renders the server-rendered `children` by default, or the selected framework's + * version of `includePath` when inside a `` with a framework selected. + */ +export function FrameworkContent({ + children, + includePath, +}: { + children: ReactNode; + includePath: string; +}) { + const context = useContext(FrameworkContext); + + if (!context?.framework) { + return children; + } + + return ( + + ); +} diff --git a/src/components/migrationGuide/index.tsx b/src/components/migrationGuide/index.tsx index b620a26031439..abd946b471f61 100644 --- a/src/components/migrationGuide/index.tsx +++ b/src/components/migrationGuide/index.tsx @@ -15,8 +15,10 @@ const MIGRATIONS = { 'flutter-v10': {storagePrefix: 'sentry-flutter-v10-migration', platform: 'dart'}, } as const; -// Share the MDX component map across item bodies and platform pages. -const components = mdxComponents(); +// Share the MDX component map across item bodies and platform pages. Built on first +// use rather than at import time: `mdxComponents` imports this module, so calling it +// while that import cycle is still resolving can read components that aren't loaded yet. +let components: ReturnType | undefined; // `getMDXComponent` compiles its source with `new Function`, so the same item // body is compiled once instead of once per page it appears on. Keyed by source, @@ -29,6 +31,7 @@ function ItemBody({mdxSource}: {mdxSource: string}) { MDXLayout = getMDXComponent(mdxSource); componentBySource.set(mdxSource, MDXLayout); } + components ??= mdxComponents(); return ; } diff --git a/src/components/platformContent.tsx b/src/components/platformContent.tsx index a4fa773303f60..7464e1f9e7b30 100644 --- a/src/components/platformContent.tsx +++ b/src/components/platformContent.tsx @@ -1,17 +1,10 @@ -import fs from 'fs'; -import {cache, useMemo} from 'react'; -import {getCurrentGuide, getDocsRootNode, getPlatform} from 'sentry-docs/docTree'; +import {useMemo} from 'react'; import {getMDXComponent} from 'sentry-docs/getMDXComponent'; -import {getFileBySlugWithCache} from 'sentry-docs/mdx'; import {mdxComponents} from 'sentry-docs/mdxComponents'; +import {resolvePlatformContent} from 'sentry-docs/platformContentResolver'; import {serverContext} from 'sentry-docs/serverContext'; -import { - getVersion, - isVersioned, - stripVersion, - VERSION_INDICATOR, -} from 'sentry-docs/versioning'; +import {FrameworkContent} from './frameworkSelector'; import {Include} from './include'; type Props = { @@ -20,124 +13,35 @@ type Props = { fallbackPlatform?: string; noGuides?: boolean; platform?: string; + /** + * Allow this content to be swapped client-side for another guide's version + * when rendered inside a ``. + */ + switchable?: boolean; }; -const updatePathIfVersionedFileDoesNotExist = (path: string): string => { - if (!isVersioned(path)) { - return path; - } - // Add .mdx extension if not present - const pathWithExtension = - path.endsWith('.mdx') || path.endsWith('.md') ? path : `${path}.mdx`; - - if (isVersioned(pathWithExtension) && !fs.existsSync(pathWithExtension)) { - return stripVersion(path); - } - - return path; -}; - -/** - * Cache the result of updatePathIfVersionedFileDoesNotExist - * to avoid calling it multiple times for the same path. - * - * This is important because we want to skip the `fs.existsSync` call if possible. - */ -const updatePathIfVersionedFileDoesNotExistWithCache = cache( - updatePathIfVersionedFileDoesNotExist -); - function MDXLayoutRenderer({mdxSource: source, ...rest}) { const MDXLayout = useMemo(() => getMDXComponent(source), [source]); return ; } -export async function PlatformContent({includePath, platform, noGuides}: Props) { +export async function PlatformContent({ + includePath, + platform, + noGuides, + switchable, +}: Props) { const {path} = serverContext(); - if (!platform) { - if (path.length < 2 || path[0] !== 'platforms') { - return null; - } - platform = path[1]; - } - - let guide: string | undefined; - if (!noGuides && path.length >= 4 && path[2] === 'guides') { - guide = `${platform}.${path[3]}`; - } - - let doc: Awaited> | undefined; - - if (guide) { - const guidePath = updatePathIfVersionedFileDoesNotExistWithCache( - `platform-includes/${includePath}/${guide}` - ); + const doc = await resolvePlatformContent({includePath, path, platform, noGuides}); - try { - doc = await getFileBySlugWithCache(guidePath); - } catch { - // It's fine - keep looking. - } - } - - if (!doc) { - const rootNode = await getDocsRootNode(); - const guideObject = getCurrentGuide(rootNode, path); - - const fallbackGuidePath = updatePathIfVersionedFileDoesNotExistWithCache( - `platform-includes/${includePath}/${guideObject?.fallbackGuide}${VERSION_INDICATOR}${getVersion(guide || '')}` - ); - - if (guideObject?.fallbackGuide) { - try { - doc = await getFileBySlugWithCache(fallbackGuidePath); - } catch { - // It's fine - keep looking. - } - } - } - - if (!doc) { - try { - const platformPath = updatePathIfVersionedFileDoesNotExistWithCache( - `platform-includes/${includePath}/${platform}` - ); - - doc = await getFileBySlugWithCache(platformPath); - } catch { - // It's fine - keep looking. - } - } - - if (!doc) { - const rootNode = await getDocsRootNode(); - const platformObject = getPlatform(rootNode, platform); - - const fallbackPlatformPath = updatePathIfVersionedFileDoesNotExistWithCache( - `platform-includes/${includePath}/${platformObject?.fallbackPlatform}` - ); - - if (platformObject?.fallbackPlatform) { - try { - doc = await getFileBySlugWithCache(fallbackPlatformPath); - } catch { - // It's fine - keep looking. - } - } - } + const content = doc ? : null; - if (!doc) { - try { - doc = await getFileBySlugWithCache(`platform-includes/${includePath}/_default`); - } catch { - // Couldn't find anything. - return null; - } + if (switchable) { + return {content}; } - const {mdxSource} = doc; - return ; + return content; } const mdxComponentsWithWrapper = mdxComponents({Include, PlatformContent}); diff --git a/src/mdxComponents.ts b/src/mdxComponents.ts index 15b0b589b8f88..7ab69cb7dc69b 100644 --- a/src/mdxComponents.ts +++ b/src/mdxComponents.ts @@ -19,6 +19,7 @@ import {DevDocsCardGrid} from './components/devDocsCardGrid'; import DocImage from './components/docImage'; import {Expandable} from './components/expandable'; import {FeatureBadge} from './components/featureBadge'; +import {FrameworkSelector} from './components/frameworkSelector'; import {GitHubCodePreview} from './components/githubCodePreview'; import {GitHubDomainChecker} from './components/githubDomainChecker'; import {GradleFeatureConfig} from './components/gradleFeatureConfig'; @@ -105,6 +106,7 @@ export function mdxComponents( DefinitionList, Expandable, FeatureBadge, + FrameworkSelector, GuideGrid, IntegrationGrid, IntegrationListItem, diff --git a/src/platformContentResolver.ts b/src/platformContentResolver.ts new file mode 100644 index 0000000000000..73c77d3acf9a7 --- /dev/null +++ b/src/platformContentResolver.ts @@ -0,0 +1,137 @@ +import fs from 'fs'; +import {cache} from 'react'; +import {getCurrentGuide, getDocsRootNode, getPlatform} from 'sentry-docs/docTree'; +import {getFileBySlugWithCache} from 'sentry-docs/mdx'; +import { + getVersion, + isVersioned, + stripVersion, + VERSION_INDICATOR, +} from 'sentry-docs/versioning'; + +type SlugFile = Awaited>; + +const updatePathIfVersionedFileDoesNotExist = (path: string): string => { + if (!isVersioned(path)) { + return path; + } + // Add .mdx extension if not present + const pathWithExtension = + path.endsWith('.mdx') || path.endsWith('.md') ? path : `${path}.mdx`; + + if (isVersioned(pathWithExtension) && !fs.existsSync(pathWithExtension)) { + return stripVersion(path); + } + + return path; +}; + +/** + * Cache the result of updatePathIfVersionedFileDoesNotExist + * to avoid calling it multiple times for the same path. + * + * This is important because we want to skip the `fs.existsSync` call if possible. + */ +const updatePathIfVersionedFileDoesNotExistWithCache = cache( + updatePathIfVersionedFileDoesNotExist +); + +/** + * Resolves the `platform-includes//...` file for a page path, walking the + * same fallback chain as ``: guide → fallbackGuide → platform → + * fallbackPlatform → `_default`. + */ +export async function resolvePlatformContent({ + includePath, + path, + platform, + noGuides, +}: { + includePath: string; + path: string[]; + noGuides?: boolean; + platform?: string; +}): Promise { + if (!platform) { + if (path.length < 2 || path[0] !== 'platforms') { + return undefined; + } + platform = path[1]; + } + + let guide: string | undefined; + if (!noGuides && path.length >= 4 && path[2] === 'guides') { + guide = `${platform}.${path[3]}`; + } + + let doc: SlugFile | undefined; + + if (guide) { + const guidePath = updatePathIfVersionedFileDoesNotExistWithCache( + `platform-includes/${includePath}/${guide}` + ); + + try { + doc = await getFileBySlugWithCache(guidePath); + } catch { + // It's fine - keep looking. + } + } + + if (!doc) { + const rootNode = await getDocsRootNode(); + const guideObject = getCurrentGuide(rootNode, path); + + const fallbackGuidePath = updatePathIfVersionedFileDoesNotExistWithCache( + `platform-includes/${includePath}/${guideObject?.fallbackGuide}${VERSION_INDICATOR}${getVersion(guide || '')}` + ); + + if (guideObject?.fallbackGuide) { + try { + doc = await getFileBySlugWithCache(fallbackGuidePath); + } catch { + // It's fine - keep looking. + } + } + } + + if (!doc) { + try { + const platformPath = updatePathIfVersionedFileDoesNotExistWithCache( + `platform-includes/${includePath}/${platform}` + ); + + doc = await getFileBySlugWithCache(platformPath); + } catch { + // It's fine - keep looking. + } + } + + if (!doc) { + const rootNode = await getDocsRootNode(); + const platformObject = getPlatform(rootNode, platform); + + const fallbackPlatformPath = updatePathIfVersionedFileDoesNotExistWithCache( + `platform-includes/${includePath}/${platformObject?.fallbackPlatform}` + ); + + if (platformObject?.fallbackPlatform) { + try { + doc = await getFileBySlugWithCache(fallbackPlatformPath); + } catch { + // It's fine - keep looking. + } + } + } + + if (!doc) { + try { + doc = await getFileBySlugWithCache(`platform-includes/${includePath}/_default`); + } catch { + // Couldn't find anything. + return undefined; + } + } + + return doc; +} From cc7ab86406d7c45a80d843866def2f0c5b86b3a3 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 14 Oct 2026 14:23:34 +0200 Subject: [PATCH 5/5] feat(js): [POC Selector 3] Name the selector "Server library" The dropdown lists only Express, Fastify, Koa and Hapi, so its name is now "Server library" instead of "Framework". A note below it links the frameworks that have their own SDK (Next.js, Nuxt, Elysia and others). Co-Authored-By: Claude Opus 5.5 --- docs/platforms/javascript/guides/bun/index.mdx | 6 ++++++ docs/platforms/javascript/guides/node/index.mdx | 6 ++++++ src/components/frameworkSelector/index.tsx | 6 +++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/platforms/javascript/guides/bun/index.mdx b/docs/platforms/javascript/guides/bun/index.mdx index 735fa5f6ff5b0..3cf1f911fbffc 100644 --- a/docs/platforms/javascript/guides/bun/index.mdx +++ b/docs/platforms/javascript/guides/bun/index.mdx @@ -9,6 +9,12 @@ categories: + + +Elysia has its own Sentry SDK. Follow the [Elysia guide](/platforms/javascript/guides/elysia/) instead. + + + diff --git a/docs/platforms/javascript/guides/node/index.mdx b/docs/platforms/javascript/guides/node/index.mdx index 3fc185556448d..9d3a502bbe25b 100644 --- a/docs/platforms/javascript/guides/node/index.mdx +++ b/docs/platforms/javascript/guides/node/index.mdx @@ -9,6 +9,12 @@ categories: + + +Frameworks like Next.js have their own Sentry SDK. Follow the guide for your framework instead: [Astro](/platforms/javascript/guides/astro/), [NestJS](/platforms/javascript/guides/nestjs/), [Next.js](/platforms/javascript/guides/nextjs/), [Nitro](/platforms/javascript/guides/nitro/), [Nuxt](/platforms/javascript/guides/nuxt/), [React Router](/platforms/javascript/guides/react-router/), [Remix](/platforms/javascript/guides/remix/), [SolidStart](/platforms/javascript/guides/solidstart/), [SvelteKit](/platforms/javascript/guides/sveltekit/), [TanStack Start](/platforms/javascript/guides/tanstackstart-react/). + + + diff --git a/src/components/frameworkSelector/index.tsx b/src/components/frameworkSelector/index.tsx index 512475168cb89..ab2b4df509c94 100644 --- a/src/components/frameworkSelector/index.tsx +++ b/src/components/frameworkSelector/index.tsx @@ -97,7 +97,7 @@ export function FrameworkSelector({
@@ -116,7 +116,7 @@ export function FrameworkSelector({