Summary
Remix 3 published its first 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)
- Does a
fetch-router middleware try/catch around next() see all handler throws, or do some become error Responses upstream? → decides whether we also need the node-fetch-server wrap for errors.
- Where to inject in
fetch-router so every router (incl. router.mount() sub-routers) gets the middleware.
- Confirm
window.navigation navigate events give clean start/end + destination URL + matched route for span naming; check pageload vs. SPA (remix/spa render()) paths.
- Any render-error hook in the
ui reconciler for component-level capture; otherwise global-handlers-only for v1.
- 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.
Summary
Remix 3 published its first 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/remixtoday assumes the Remix 2 internals:@remix-run/server-runtimeinternals —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.instrumentServer.tsfillscreateRequestHandlerfrom@remix-run/server-runtime— same non-existent module.@sentry/react(withSentryReact error boundaries, React-Router browser tracing / route parameterization). With React gone, the entire client is inapplicable.@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 withrouter.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)logger-middleware,session-middleware,cors-middleware, …)No
diagnostics_channelusage was found in the framework, so there is no native channel to subscribe to.Proposed approach
Packaging — keep it in
@sentry/remixOne 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.xduring RC) each no-op on the other major. End-user experience stays: install@sentry/remix, done. Package exports stayindex.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 (
registrationOnlyinpackages/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, withcontext.request/url/typedparamsand the ability to run before and afterawait next()— handles:http.serverspan, runnext()inside it, set status from the returnedResponse, close it;continueTracefrom incoming headers + injectsentry-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 errorResponseupstream first. If middleware misses some, add a second orchestrion patch at thenode-fetch-serverrequest-handler choke point (the v3 analog of wrappingcreateRequestHandlertoday).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/browserNo React →
@sentry/reactis out.@remix-run/uigives us:run(options)(returnsappwithapp.ready()) — wrap to init Sentry and catch init/hydration errors.window.navigation, so pageload + navigation spans subscribe tonavigateevents instead of patching History. Route parameterization from the router's matched pattern. Strongest reuse point on the client.onError/ component error boundary inrun()/app. Client error capture for v1 leans on@sentry/browserglobalonerror/unhandledrejectionhandlers + therun()wrap. Whether theuireconciler 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)
fetch-routermiddlewaretry/catcharoundnext()see all handler throws, or do some become errorResponses upstream? → decides whether we also need thenode-fetch-serverwrap for errors.fetch-routerso every router (incl.router.mount()sub-routers) gets the middleware.window.navigationnavigateevents give clean start/end + destination URL + matched route for span naming; check pageload vs. SPA (remix/sparender()) paths.uireconciler for component-level capture; otherwise global-handlers-only for v1.versionRangeand optional peer deps (still0.xRC).v1 scope
In: server
http.serverspan + 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.