You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'd like to propose adding first-class TracingChannel support to TanStack Start, following the pattern established by undici in Node.js core and adopted by framework peers like h3, fastify, and srvx.
TracingChannel is a higher-level API over diagnostics_channel, built for tracing async operations. It exposes structured lifecycle channels (start, end, error, asyncStart, asyncEnd) and propagates async context correctly — the piece that makes monkey-patching fragile in real-world async code.
Today, APM instrumentation monkey-patches framework internals via IITM (ESM) and RITM (CJS). This is fragile in several ways:
Runtime lock-in: both rely on Node-specific module loader internals (Module._resolveFilename, module.register()) and don't work on Bun, Deno, or Cloudflare Workers.
ESM fragility: IITM rides on Node's still-evolving module hooks, a persistent source of breakage in the OTel JS ecosystem.
Init ordering: instrumentation must load before the framework is first imported.
Under these limitations, our SDK @sentry/tanstackstart-react uses three coordinated mechanisms:
A Vite build plugin that regex-transforms middleware arrays in createStart(), createFileRoute(), and createServerFn().middleware() to inject wrappers.
wrapFetchWithSentry, a Proxy on the server entry's fetch that intercepts every request, detects server functions by URL pattern (_serverFn), and parses HTML response streams to inject trace meta-tags.
wrapMiddlewaresWithSentry, a Proxy on each middleware's next() for span lifecycle, with symbol-based dedup to avoid double-wrapping.
That's a lot of fragile machinery for basic request tracing. If TanStack Start emits structured TracingChannel events, instrumentation becomes subscribers, not patches, each tool listens independently, with no ordering concerns, no clobbering, and no internal-API dependency.
Then there is also the missing aspect of the SSR boundary tracing, being able to identify and flag expensive components can be very useful for APMs and users alike.
Proposed Channels
Proposed Tracing Channels
Two narrow channels, each with its own subscriber set:
TracingChannel
What it traces
tanstack-start:request
The executeMiddleware() cascade — middleware, route handlers, and server-function invocations.
tanstack-start:render
The SSR document render for a page request (executeRouter() — route load, dehydrate, render-to-stream).
Separate or fine-grained channels are encouraged here so that APMs do not incur overhead for the channels that they may not be interested in, and this would mean the user can potentially choose which channels matter to them.
The exact payloads for these channels are unclear to me at this point, but I think a PR will make them more concrete. But generally speaking the payloads would contain request context, the request object, matched route, etc...
I think there are opportunities for more channels, but it is easier to add more in the future so this proposal doesn't have to cover all instrumentation surfaces.
Usage Example
An APM/users would only need to subscribe to the channels to build their instrumentation:
constdc=require('node:diagnostics_channel');// TracingChannel automatically propagates this span as the active context.// Middleware cascade nests naturally via async context, no manual wrapping needed.dc.tracingChannel('@tanstack/start:request').subscribe({start(ctx){// could create a span, a log, a metric or whatever kind of telemetry needed hereconstspanName="....";ctx.span=tracer.startSpan(spanName,{kind: ctx.type==='handler' ? SpanKind.SERVER : SpanKind.INTERNAL,attributes: {// ...},});},asyncEnd(ctx){// End span, set status, etc...},error(ctx){// Set span error status, capture errors, etc...},});// Subscribe to the SSR render and per-route loaders the same way.// All three publish within the same async context, so spans nest into one tree.dc.tracingChannel('@tanstack/start:render').subscribe({// Spans for SSR rendering...});
Prior Art
undici (Node.js core): ships TracingChannel support since Node 20.12 (undici:request)
fastify: ships TracingChannel support natively (tracing:fastify.request.handler)
We talked about this in Discord, but we would be happy to submit a PR for it and take it from there. My question here is, does Tanstack intend to keep using h3? I saw some talks about dropping it and this can affect the changes here, since we can either re-use h3's already existing channels by just enabling them or we could build our own channels to keep things flexible in the future.
I can also see tanstack/db, tanstack/ai using tracing channels as well but I can create separate proposals for them.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Overview
I'd like to propose adding first-class
TracingChannelsupport to TanStack Start, following the pattern established byundiciin Node.js core and adopted by framework peers likeh3,fastify, andsrvx.TracingChannelis a higher-level API overdiagnostics_channel, built for tracing async operations. It exposes structured lifecycle channels (start,end,error,asyncStart,asyncEnd) and propagates async context correctly — the piece that makes monkey-patching fragile in real-world async code.Today, APM instrumentation monkey-patches framework internals via IITM (ESM) and RITM (CJS). This is fragile in several ways:
Module._resolveFilename,module.register()) and don't work on Bun, Deno, or Cloudflare Workers.Under these limitations, our SDK
@sentry/tanstackstart-reactuses three coordinated mechanisms:createStart(),createFileRoute(), andcreateServerFn().middleware()to inject wrappers.wrapFetchWithSentry, aProxyon the server entry'sfetchthat intercepts every request, detects server functions by URL pattern (_serverFn), and parses HTML response streams to inject trace meta-tags.wrapMiddlewaresWithSentry, aProxyon each middleware'snext()for span lifecycle, with symbol-based dedup to avoid double-wrapping.That's a lot of fragile machinery for basic request tracing. If TanStack Start emits structured
TracingChannelevents, instrumentation becomes subscribers, not patches, each tool listens independently, with no ordering concerns, no clobbering, and no internal-API dependency.Then there is also the missing aspect of the SSR boundary tracing, being able to identify and flag expensive components can be very useful for APMs and users alike.
Proposed Channels
Proposed Tracing Channels
Two narrow channels, each with its own subscriber set:
tanstack-start:requestexecuteMiddleware()cascade — middleware, route handlers, and server-function invocations.tanstack-start:renderexecuteRouter()— route load, dehydrate, render-to-stream).Separate or fine-grained channels are encouraged here so that APMs do not incur overhead for the channels that they may not be interested in, and this would mean the user can potentially choose which channels matter to them.
The exact payloads for these channels are unclear to me at this point, but I think a PR will make them more concrete. But generally speaking the payloads would contain request context, the request object, matched route, etc...
I think there are opportunities for more channels, but it is easier to add more in the future so this proposal doesn't have to cover all instrumentation surfaces.
Usage Example
An APM/users would only need to subscribe to the channels to build their instrumentation:
Prior Art
undici(Node.js core): shipsTracingChannelsupport since Node 20.12 (undici:request)fastify: shipsTracingChannelsupport natively (tracing:fastify.request.handler)h3: h3js/h3#1251 (h3.request)srvx: h3js/srvx#141 (srvx.request,srvx.middleware)nitro: nitrojs/nitro#4001nuxt: nuxt/nuxt#35191graphql: graphql/graphql-js#4670mongoose: Automattic/mongoose#16275We talked about this in Discord, but we would be happy to submit a PR for it and take it from there. My question here is, does Tanstack intend to keep using
h3? I saw some talks about dropping it and this can affect the changes here, since we can either re-use h3's already existing channels by just enabling them or we could build our own channels to keep things flexible in the future.I can also see
tanstack/db,tanstack/aiusing tracing channels as well but I can create separate proposals for them.Beta Was this translation helpful? Give feedback.
All reactions