Skip to content

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).

Terminal window
npm install autotel-tanstack autotel

Configure global middleware in start.ts:

src/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],
}));
src/instrumentation.ts
import { init } from 'autotel';
init({
service: 'my-app',
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
});

Skip the explicit init() and use environment variables instead:

src/instrumentation.ts
import 'autotel-tanstack/auto';
export {};

autotel-tanstack/auto reads OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, and OTEL_EXPORTER_OTLP_HEADERS at startup.

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 });
});

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 }) => { /* ... */ });
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);
}),
});

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 |

  • http.request.methodGET, POST, etc.
  • url.path, url.query
  • http.response.status_code
  • tanstack.request.duration_ms
  • rpc.systemtanstack-start
  • rpc.method — function name
  • tanstack.server_function.name
  • tanstack.server_function.method
  • tanstack.server_function.args — when captureArgs: true
  • tanstack.loader.route_id
  • tanstack.loader.typeloader or beforeLoad
  • tanstack.loader.params

| 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 |

Inject and extract W3C trace context for cross-service calls:

import {
createTracedHeaders,
extractContextFromRequest,
} from 'autotel-tanstack/context';
// Outgoing
const headers = createTracedHeaders({ 'Content-Type': 'application/json' });
await fetch('https://api.example.com', { headers, method: 'POST', body });
// Incoming
const parentContext = extractContextFromRequest(request);

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.

Terminal window
# playwright.config.ts webServer
E2E=1 OTEL_SERVICE_NAME=my-app vite preview --port 3100

Expose the spans over HTTP:

src/routes/api/test-spans.ts
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.

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');
});
});

| 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 |

  • @tanstack/react-start ^1.139.14
  • @tanstack/solid-start ^1.139.14
  • example-tanstack-start — full-stack with request and server-function middleware, route loaders, and autotel-web for browser trace propagation.