FlareLog SDK - TanStack Start Guide
Zero-config logging for TanStack Start applications. Automatically capture request logs, errors, and performance metrics with trace IDs.
TanStack Start does not expose an app.use(...) API. FlareLog integrates via TanStack Start's createMiddleware() from @tanstack/react-start. Register the middleware globally, per-route, or per server function.
Quick Start
// src/start.ts
import { createStart } from "@tanstack/react-start";
import { flarelog } from "@flarelog/sdk";
import { tanstackStartMiddleware } from "@flarelog/sdk/tanstack-start";
const logger = flarelog({ apiKey: process.env.FLARELOG_API_KEY! });
export const startInstance = createStart(() => ({
requestMiddleware: [tanstackStartMiddleware(logger) as never],
}));
tanstackStartMiddleware(logger)returns a TanStack Start middleware built withcreateMiddleware(). Theas nevercast may be needed depending on yourcreateStarttype parameters; if your TS setup infers the builder type directly, omit it.
The flarelog() factory auto-detects environment, release, and serverName.
Installation
npm install @flarelog/sdk @tanstack/react-startMiddleware Setup
Global Request Middleware
Runs before every request handled by Start — server routes, SSR, and server functions. Define it in src/start.ts:
// src/start.ts
import { createStart } from "@tanstack/react-start";
import { flarelog } from "@flarelog/sdk";
import { tanstackStartMiddleware } from "@flarelog/sdk/tanstack-start";
const logger = flarelog({ apiKey: process.env.FLARELOG_API_KEY! });
export const startInstance = createStart(() => ({
requestMiddleware: [tanstackStartMiddleware(logger) as never],
}));If you define
src/start.ts, also addcreateCsrfMiddleware()explicitly — Start only auto-installs CSRF protection when nosrc/start.tsexists.
Per-Route Middleware
import { createFileRoute } from "@tanstack/react-router";
import { flarelog } from "@flarelog/sdk";
import { tanstackStartMiddleware } from "@flarelog/sdk/tanstack-start";
const logger = flarelog({ apiKey: process.env.FLARELOG_API_KEY! });
export const Route = createFileRoute("/api/users/$id")({
server: {
middleware: [tanstackStartMiddleware(logger) as never],
handlers: {
GET: async ({ context }) => {
// context.logger is the FlareLog child logger
context.logger.info("Fetching user");
return new Response(JSON.stringify({ ok: true }));
},
},
},
});Per Server Function Middleware
import { createServerFn } from "@tanstack/react-start";
import { flarelog } from "@flarelog/sdk";
import { tanstackStartMiddleware } from "@flarelog/sdk/tanstack-start";
const logger = flarelog({ apiKey: process.env.FLARELOG_API_KEY! });
export const getUser = createServerFn({ method: "GET" })
.middleware([tanstackStartMiddleware(logger) as never])
.handler(async ({ context }) => {
context.logger.info("getUser called");
return { id: 1, name: "Ada" };
});Using the Logger in Handlers
The middleware merges logger (a FlareLogChild) and traceId into the downstream context. Access them directly on context:
export const Route = createFileRoute("/api/orders/$id")({
server: {
middleware: [tanstackStartMiddleware(logger) as never],
handlers: {
GET: async ({ context, params }) => {
const orderLogger = context.logger.child({ operation: "fetch-order" });
orderLogger.info("Fetching order", { orderId: params.id });
const order = await db.orders.find(params.id);
if (!order) {
orderLogger.warn("Order not found", { orderId: params.id });
return new Response("Not Found", { status: 404 });
}
orderLogger.info("Order fetched", { orderId: order.id });
return Response.json(order);
},
},
},
});What Gets Logged Automatically
Request Completion
Every request is logged with:
- Trace ID: From
x-trace-idrequest header or auto-generated UUID - Method: HTTP method (GET, POST, etc.)
- Path: Request URL pathname
- Duration: Request duration in milliseconds
- Status (when available): TanStack Start exposes status setters (
setResponseStatus) but not a status reader from within request middleware. When thenext()result carries a numericstatus, it is used for log-level mapping; otherwise completion logs at INFO.
Log Levels by Status Code (when status is available)
| Status Range | Log Level |
|---|---|
| 2xx-3xx | INFO |
| 4xx | WARN |
| 5xx | ERROR |
Error Capture
Unhandled errors thrown downstream are automatically captured with:
- Full error stack trace
- Request context (method, path, traceId)
- Duration at point of failure
The error is re-thrown after logging so TanStack Start's normal error handling still runs.
Child Loggers
Create contextual loggers for specific operations:
context.logger.child({ source: "order-service", operation: "create-order" });Custom Trace ID Header
The middleware reads x-trace-id. To use a different header, wrap it with a small custom middleware that rewrites the request first:
import { createMiddleware } from "@tanstack/react-start";
import { tanstackStartMiddleware } from "@flarelog/sdk/tanstack-start";
const renameTraceHeader = createMiddleware().server(async ({ next, request }) => {
const altTraceId = request.headers.get("x-request-id");
if (altTraceId) {
const headers = new Headers(request.headers);
headers.set("x-trace-id", altTraceId);
return next({ request: new Request(request, { headers }) });
}
return next();
});
// Apply both middlewares in order
export const startInstance = createStart(() => ({
requestMiddleware: [renameTraceHeader, tanstackStartMiddleware(logger)],
}));Adding User Context
export const Route = createFileRoute("/api/protected")({
beforeLoad: async ({ context }) => {
const user = await getUser();
if (user) {
context.logger.setUser({ id: user.id, email: user.email, name: user.name });
}
return { user };
},
});Environment Variables
# .env
FLARELOG_API_KEY=fl_your_api_key
FLARELOG_ENVIRONMENT=production
FLARELOG_RELEASE=1.2.3
FLARELOG_SERVER_NAME=tanstack-start
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-eu-west-0.grafana.net
OTEL_RESOURCE_ATTRIBUTES=service.name=my-app// app.config.ts
import { flarelog } from "@flarelog/sdk";
export const logger = flarelog({
apiKey: process.env.FLARELOG_API_KEY!,
environment: process.env.FLARELOG_ENVIRONMENT,
release: process.env.FLARELOG_RELEASE,
});Best Practices
- Always use the context logger: Access via
context.loggerto keep trace context. Do not import the root logger into handlers. - Log early: Log at the start of loaders and handlers.
- Include IDs: Add userId, orderId, etc. to every log.
- Use child loggers: Create scoped loggers for complex operations.
- Set user context: Identify authenticated users when possible.
- Add breadcrumbs: Track multi-step operations.
- Handle errors: Use
logError()for structured error reporting.
Integration with React
Combine with the React Error Boundary for full-stack coverage:
// app.tsx
import { FlareLogErrorBoundary } from "@flarelog/sdk/react";
import { flarelog } from "@flarelog/sdk";
const logger = flarelog({ apiKey: process.env.FLARELOG_API_KEY! });
export default function App() {
return (
<FlareLogErrorBoundary logger={logger}>
<Router />
</FlareLogErrorBoundary>
);
}TypeScript Support
@tanstack/react-start is an optional peer dependency. When installed, TypeScript resolves the middleware builder types against the real package. The SDK exports a loosely-typed return from tanstackStartMiddleware so it composes with createStart, createFileRoute, and createServerFn middleware arrays without forcing a specific builder type.
Migration from withTanStackStart
The previous withTanStackStart(logger, handler) wrapper was built against an app.use-style API that TanStack Start does not provide. It is now a deprecated stub that throws on invocation. Migrate to:
createServerFn()
.middleware([tanstackStartMiddleware(logger) as never])
.handler(async ({ context }) => {
context.logger.info("Processing");
return /* ... */;
});