Frameworks and runtimes
Next.js, NestJS and Express, queues and scripts, Vercel functions and AWS Lambda.
On this page
The rule is the same everywhere: the SDK has to be loaded before any provider
client is constructed, because clients capture fetch when they are created.
What changes is who owns startup.
Any Node server
Express, Fastify, Hono, Koa, a plain http.Server — add the preload flag to the
command you already run.
node --import @tracehatch/sdk/auto server.jsEvery inbound request handled by http.Server or https.Server that makes a
model call becomes a run named METHOD /path, marked failed on a 5xx status.
Next.js
Next.js owns startup, so it loads the SDK from instrumentation.ts instead.
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("@tracehatch/sdk/auto")
}
}
- Save it beside your
apporpagesdirectory — insidesrcwhen your app usessrc/app. Merge into an existingregister()if you have one. - Keep the key in
.env.local. - Use the Node.js runtime for AI routes. The Edge runtime and browser code are not supported by the Node SDK.
- Route handlers finish before export does, so hand the last flush to
after():
import { after } from "next/server"
import { flush } from "@tracehatch/sdk"
export async function POST(request: Request) {
const response = await answer(request)
after(() => flush())
return response
}NestJS
import "@tracehatch/sdk/auto"
import { NestFactory } from "@nestjs/core"
import { AppModule } from "./app.module"
async function bootstrap() {
const app = await NestFactory.create(AppModule)
await app.listen(3000)
}
void bootstrap()This works for standard compiled NestJS entrypoints in CommonJS and NodeNext. Custom entrypoints, alternate CLI configurations and Nest monorepo layouts need the same import placed by hand — the installer reports these rather than guessing at them.
Flush during graceful shutdown if you enable Nest's shutdown hooks; otherwise the SDK's own exit handling covers it.
Scripts, queues and workers
Outside an inbound request, a model call starts a run. If the response asks for tools, the run stays open for the tool step and the follow-up call with the results; it ends on a response without tool calls, a failure, a new user message, ten idle minutes, or process exit.
Name the unit of work yourself when "one model call" is not the unit you care about:
import { trace, flush } from "@tracehatch/sdk"
for await (const job of queue) {
await trace(`process ${job.type}`, async (run) => {
run.setMetadata({ jobId: job.id })
return handle(job)
})
}
await flush()Vercel functions
Nothing extra: the final flush is handed to waitUntil. Configure
TRACEHATCH_API_KEY in the project's server environment variables, never a
NEXT_PUBLIC_ one.
AWS Lambda, Netlify and similar
The process can be frozen the instant the handler returns, so wait for export:
import "@tracehatch/sdk/auto"
import { flush } from "@tracehatch/sdk"
export async function handler(event) {
try {
return await answer(event)
} finally {
await flush()
}
}Load the SDK through the layer's preload option (NODE_OPTIONS) when the
handler file is not the first module to run.
TypeScript runners
tsx accepts Node CLI flags as a documented drop-in replacement, so
tsx --import @tracehatch/sdk/auto server.ts works. ts-node and
ts-node-dev are recognised by the installer when it edits your scripts. A
script already using --require is reported rather than rewritten.
Not supported
| Runtime | Use instead |
|---|---|
| Browsers | Nothing. Never ship the key or the SDK to a client. |
| Edge runtimes and Workers | Provider gateway |
| Deno | Provider gateway |
| Python and other languages | Gateway or log endpoint |
Bun runs the package but its compatibility is unverified.
Frameworks on top of a provider client
Agent frameworks that call a supported API shape through the global fetch are
captured with no extra work, because capture sits below them at the HTTP
boundary. A framework that ships its own HTTP stack, or that is handed a custom
fetch, is not — record those calls with a
manual generation span.