TanStack Start
autotel-tanstack instruments TanStack Start
applications using the framework’s native middleware patterns. Configure global
middleware once and every server function, route loader, and HTTP request is
traced automatically. Works with both @tanstack/react-start and
@tanstack/solid-start (^1.139.14).
Installation
Section titled “Installation”npm install autotel-tanstack autotelQuick start
Section titled “Quick start”Configure global middleware in start.ts:
import { createStart, createMiddleware } from '@tanstack/react-start';import { createTracingServerHandler } from 'autotel-tanstack/middleware';import './instrumentation';
const requestTracingMiddleware = createMiddleware().server( createTracingServerHandler({ captureHeaders: ['x-request-id', 'user-agent'], excludePaths: ['/health', '/metrics'], }),);
const functionTracingMiddleware = createMiddleware({ type: 'function' }).server( createTracingServerHandler({ type: 'function', captureArgs: true }),);
export const startInstance = createStart(() => ({ requestMiddleware: [requestTracingMiddleware], functionMiddleware: [functionTracingMiddleware],}));import { init } from 'autotel';
init({ service: 'my-app', endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,});Zero-config alternative
Section titled “Zero-config alternative”Skip the explicit init() and use environment variables instead:
import 'autotel-tanstack/auto';export {};autotel-tanstack/auto reads OTEL_SERVICE_NAME,
OTEL_EXPORTER_OTLP_ENDPOINT, and OTEL_EXPORTER_OTLP_HEADERS at startup.
Server functions
Section titled “Server functions”With functionMiddleware configured globally, every createServerFn(...) is
auto-traced:
import { createServerFn } from '@tanstack/react-start';
export const getUser = createServerFn({ method: 'GET' }).handler( async ({ data: id }) => { return await db.users.findUnique({ where: { id } }); },);
export const createUser = createServerFn({ method: 'POST' }) .validator((d: UserInput) => d) .handler(async ({ data }) => { return await db.users.create({ data }); });Per-function middleware
Section titled “Per-function middleware”For per-function options (e.g. capturing results for one operation), chain a function-scoped middleware:
import { createServerFn, createMiddleware } from '@tanstack/react-start';import { createTracingServerHandler } from 'autotel-tanstack/middleware';
const verboseTracing = createMiddleware({ type: 'function' }).server( createTracingServerHandler({ type: 'function', captureArgs: true, captureResults: true, }),);
export const sensitiveOperation = createServerFn({ method: 'POST' }) .middleware([verboseTracing]) .handler(async ({ data }) => { /* ... */ });Route loaders
Section titled “Route loaders”import { createFileRoute, redirect } from '@tanstack/react-router';import { traceLoader, traceBeforeLoad } from 'autotel-tanstack/loaders';
export const Route = createFileRoute('/users/$userId')({ beforeLoad: (ctx) => traceBeforeLoad(ctx, async ({ context }) => { if (!context.auth.isAuthenticated) throw redirect({ to: '/login' }); }), loader: (ctx) => traceLoader(ctx, async ({ params }) => { return await getUser(params.userId); }),});Pass the route’s ctx through. TanStack Router types params, search and the context from the function you write, and the traced function receives them typed.
Middleware options
Section titled “Middleware options”createTracingServerHandler accepts:
| Option | Type | Default | Description |
|---|---|---|---|
type |
'request' | 'function' |
'request' |
Span kind for this middleware |
captureHeaders |
string[] |
[] |
Request headers to attach as span attributes |
captureArgs |
boolean |
true |
Record server-function arguments |
captureResults |
boolean |
false |
Record server-function results (off by default; PII risk) |
excludePaths |
string[] |
[] |
Glob paths to skip |
sampling |
'always' | 'adaptive' | 'never' |
'adaptive' |
Sampling strategy |
customAttributes |
({ type, name, request }) => Attributes |
— | Per-span custom attributes |
Span attributes
Section titled “Span attributes”HTTP request spans
Section titled “HTTP request spans”http.request.method:GET,POST, etc.url.path,url.queryhttp.response.status_codetanstack.request.duration_ms
Server function spans
Section titled “Server function spans”rpc.system:tanstack-startrpc.method: function nametanstack.server_function.nametanstack.server_function.methodtanstack.server_function.args: whencaptureArgs: true
Loader spans
Section titled “Loader spans”tanstack.loader.route_idtanstack.loader.type:loaderorbeforeLoadtanstack.loader.params
Environment variables
Section titled “Environment variables”| Variable | Description | Example |
|---|---|---|
OTEL_SERVICE_NAME |
Service name reported on every span | my-app |
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP collector URL | https://api.honeycomb.io |
OTEL_EXPORTER_OTLP_HEADERS |
Auth headers (key=value,key=value) |
x-honeycomb-team=YOUR_KEY |
AUTOTEL_DEBUG |
Enable debug logging | true |
E2E |
Switch to in-memory exporter for tests | 1 |
Distributed tracing
Section titled “Distributed tracing”Inject and extract W3C trace context for cross-service calls:
import { createTracedHeaders, extractContextFromRequest,} from 'autotel-tanstack/context';
// Outgoingconst headers = createTracedHeaders({ 'Content-Type': 'application/json' });await fetch('https://api.example.com', { headers, method: 'POST', body });
// Incomingconst parentContext = extractContextFromRequest(request);E2E testing
Section titled “E2E testing”Set E2E=1 in your Playwright webServer command. autotel-tanstack/auto
detects this and switches to an in-memory exporter so spans can be asserted on
instead of being shipped to OTLP.
# playwright.config.ts webServerE2E=1 OTEL_SERVICE_NAME=my-app vite preview --port 3100Expose the spans over HTTP:
import { createFileRoute } from '@tanstack/react-router';import { createTestSpansHandlers } from 'autotel-tanstack/testing';
const { GET, DELETE } = createTestSpansHandlers();
export const Route = createFileRoute('/api/test-spans')({ server: { handlers: { GET, DELETE } },});Then assert from Playwright using
createTestSpansClient
from autotel-playwright.
Unit testing
Section titled “Unit testing”import { describe, it, expect } from 'vitest';import { createTestCollector } from 'autotel-tanstack/testing';
describe('getUser', () => { it('creates a span', async () => { const collector = createTestCollector(); await getUser({ data: '123' }); const spans = collector.getSpans(); expect(spans[0].name).toContain('getUser'); });});Entry points
Section titled “Entry points”| Import | Contents |
|---|---|
autotel-tanstack |
Everything |
autotel-tanstack/auto |
Zero-config auto-instrumentation |
autotel-tanstack/middleware |
createTracingServerHandler |
autotel-tanstack/server-functions |
traceServerFn (explicit wrapper) |
autotel-tanstack/loaders |
traceLoader, traceBeforeLoad |
autotel-tanstack/handlers |
wrapStartHandler (handler-level wrapping) |
autotel-tanstack/context |
createTracedHeaders, extractContextFromRequest |
autotel-tanstack/testing |
createTestCollector, createTestSpansHandlers |
Supported frameworks
Section titled “Supported frameworks”@tanstack/react-start^1.139.14@tanstack/solid-start^1.139.14
Examples
Section titled “Examples”example-tanstack-start: full-stack with request and server-function middleware, route loaders, andautotel-webfor browser trace propagation.