getsentry / getsentry/sentry-javascript
Support Remix 3 (v3 rewrite) in @sentry/remix
- Dominant language
- TypeScript
- Stars
- 8.7k
- Forks
- 1.8k
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 515
Description
## Summary
Remix 3 published its first [release candidate](https://remix.run/blog/remix-3-release-candidate) on 2026-08-31. It is a **ground-up rewrite, not a version bump**: it drops React entirely (its UI runtime is a fork of Preact with its own component model — no virtual DOM, explicit updates instead of hooks), drops React Router, and rebuilds around the Fetch API (`Request`/`Response`) so it runs on any Fetch runtime (Node, Deno, Bun). It ships as a new set of composable packages, **none of which are the `@remix-run/*` packages our current SDK targets**.
This issue tracks adding Remix 3 support to `@sentry/remix`.
## Why the current SDK is fully incompatible
Everything in `@sentry/remix` today assumes the Remix 2 internals:
- **Server (orchestrion):** our tracing channels target `@remix-run/server-runtime` internals — `requestHandler`, `matchServerRoutes`, `callRouteLoader`, `callRouteAction` — version-gated `>=2.0.0 <3` (`packages/server-utils/src/orchestrion/config/remix.ts`). None of these exist in v3; there is no `@remix-run/server-runtime`.
- **Server (monkey-patch fallback):** `instrumentServer.ts` fills `createRequestHandler` from `@remix-run/server-runtime` — same non-existent module.
- **Client:** we are a wrapper around `@sentry/react` (`withSentry` React error boundaries, React-Router browser tracing / route parameterization). With React gone, the entire client is inapplicable.
- **Deps:** peer deps are `@remix-run/{node,react,server-runtime} 2.x` + `react 18.x`; all irrelevant to v3.
## Relevant Remix 3 packages
- `@remix-run/fetch-router` — composable Fetch-API router with `router.mount()` and a **middleware system**
- `@remix-run/node-fetch-server` — Node adapter for Fetch-API servers
- `@remix-run/ui` — the new reconciler + component model (the Preact fork)
- a suite of middleware packages (`logger-middleware`, `session-middleware`, `cors-middleware`, …)
No `diagnostics_channel` usage was found in the framework, so there is no native channel to subscribe to.
## Proposed approach
### Packaging — keep it in `@sentry/remix`
One package covers both majors. The two runtimes share nothing but never collide: orchestrion configs are gated by the module + version they patch, so the v2 config (`@remix-run/server-runtime` `>=2 <3`) and a new v3 config (`@remix-run/fetch-router` / `@remix-run/node-fetch-server`, `>=3` / `0.x` during RC) each no-op on the other major. End-user experience stays: install `@sentry/remix`, done. Package exports stay `index.server` / `index.client`; we branch internally on which framework is present. Peer deps widen so both the v2 and v3 `@remix-run/*` sets are optional.
### Server — orchestrion auto-injects a Sentry middleware (zero config)
Reuse the existing module-injected-registration machinery (`registrationOnly` in `packages/server-utils/src/orchestrion/config/registration-only.ts`, `bundler/moduleInjectedTransform.ts`): orchestrion transforms `@remix-run/fetch-router`'s router construction at load time and **prepends our Sentry middleware** to every router — no manual wiring by the user. Orchestrion's role is auto-registration; the span logic lives in normal SDK code (the middleware).
The middleware — `(context, next) => Response`, with `context.request`/`url`/typed `params` and the ability to run before and after `await next()` — handles:
- open the `http.server` span, run `next()` inside it, set status from the returned `Response`, close it;
- name the transaction from the matched route pattern (low-cardinality / span-streaming logic we already have);
- `continueTrace` from incoming headers + inject `sentry-trace` / `baggage`.
**Error capture:** try/catch around `next()` → `captureException` → rethrow. Open question (spike): whether every handler throw surfaces at the middleware boundary or gets converted to an error `Response` upstream first. If middleware misses some, add a second orchestrion patch at the `node-fetch-server` request-handler choke point (the v3 analog of wrapping `createRequestHandler` today).
**Manual fallback:** also export the middleware (e.g. `Sentry.remixMiddleware()`) for bundlers/runtimes where orchestrion injection doesn't fire — matches our usual "auto, with a manual escape hatch" pattern.
**Sub-request spans: out of scope for v1.** No loader/action split exists in v3 (routes are Fetch handlers returning `Response`), so the middleware request span is the whole server story initially.
### Client — build on `@sentry/browser`
No React → `@sentry/react` is out. `@remix-run/ui` gives us:
- **Wrappable entry `run(options)`** (returns `app` with `app.ready()`) — wrap to init Sentry and catch init/hydration errors.
- **Navigation via the standard Navigation API** — the runtime intercepts same-origin links/forms through `window.navigation`, so pageload + navigation spans subscribe to `navigate` events instead of patching History. Route parameterization from the router's matched pattern. Strongest reuse point on the client.
- **Missing: no documented `onError` / component error boundary** in `run()`/`app`. Client error capture for v1 leans on `@sentry/browser` global `onerror`/`unhandledrejection` handlers + the `run()` wrap. Whether the `ui` reconciler exposes a render-error hook is a spike item; if not, global handlers are the v1 answer and we document the gap.
## Spike checklist (against the RC, before committing)
1. Does a `fetch-router` middleware `try/catch` around `next()` see all handler throws, or do some become error `Response`s upstream? → decides whether we also need the `node-fetch-server` wrap for errors.
2. Where to inject in `fetch-router` so every router (incl. `router.mount()` sub-routers) gets the middleware.
3. Confirm `window.navigation` `navigate` events give clean start/end + destination URL + matched route for span naming; check pageload vs. SPA (`remix/spa` `render()`) paths.
4. Any render-error hook in the `ui` reconciler for component-level capture; otherwise global-handlers-only for v1.
5. Confirm v3 package/version identifiers for the orchestrion `versionRange` and optional peer deps (still `0.x` RC).
## v1 scope
**In:** server `http.server` span + trace propagation + route naming (auto-injected middleware); server error capture; client init + pageload/navigation tracing (Navigation API); client global error capture.
**Out:** sub-request spans; component-level client error boundaries (pending a hook); anything depending on APIs still moving in the RC.
Contributor guide
Assessment
This issue has not been assessed yet.