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' }) .inputValidator((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: traceBeforeLoad(async ({ context }) => { if (!context.auth.isAuthenticated) throw redirect({ to: '/login' }); }), loader: traceLoader(async ({ params }) => { return await getUser(params.userId); }),});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.