Source map resolution for OpenTelemetry errors. Automatically resolves minified stack traces back to original source locations using debug IDs.
- Build time: Source maps are collected and stored (locally or in a remote store)
- Client side: Exception stack traces are enriched with debug IDs from bundler-injected globals (
_debugIdsor__DEBUG_IDS__) - Server side: A request handler resolves minified stack traces using the stored source maps before forwarding to your OTEL collector
| Package | Description |
|---|---|
smapped-traces |
Core library — client exporter, route handler, source map resolver, and store abstraction |
@smapped-traces/nextjs |
Next.js plugin — build-time source map collection via withSourceMaps() |
@smapped-traces/vite |
Vite plugin — build-time source map collection via sourceMaps() and collectSourceMaps() |
@smapped-traces/sqlite |
SQLite-backed source map store for local and single-server deployments |
@smapped-traces/s3 |
S3-compatible source map store (AWS S3, GCS, Cloudflare R2) |
npm install smapped-traces @smapped-traces/nextjs @smapped-traces/sqlite// next.config.mjs
import { withSourceMaps } from "@smapped-traces/nextjs";
import { createSqliteStore } from "@smapped-traces/sqlite";
import { join } from "node:path";
export default withSourceMaps(
{
// your config
},
{
store: (distDir) => createSqliteStore(join(distDir, "sourcemaps.db")),
}
);// instrumentation-client.ts
import { SourceMappedSpanExporter } from "smapped-traces/client";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
const exporter = new SourceMappedSpanExporter("/api/sourcemaps");
const provider = new WebTracerProvider({
spanProcessors: [new SimpleSpanProcessor(exporter)],
});
provider.register();// app/api/sourcemaps/route.ts
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { createTracesHandler } from "smapped-traces/route";
import { createSqliteStore } from "@smapped-traces/sqlite";
import { join } from "node:path";
const OTLP_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318";
export const POST = createTracesHandler({
exporter: new OTLPTraceExporter({ url: `${OTLP_ENDPOINT}/v1/traces` }),
store: createSqliteStore(join(process.cwd(), ".next/sourcemaps.db")),
});Source maps are accessed through the SourceMapStore interface. Built-in stores:
| Store | Package | Description |
|---|---|---|
createSqliteStore(dbPath) |
@smapped-traces/sqlite |
Local SQLite database |
createHttpStore(url) |
smapped-traces/store |
HTTP client for a remote store handler |
createS3Store(options) |
@smapped-traces/s3 |
S3-compatible bucket (AWS, GCS, R2) |
Deploy a storage service and point your build + handler at it:
// storage-service.ts — deploy separately
import { createStoreHandler } from "smapped-traces/store";
import { createSqliteStore } from "@smapped-traces/sqlite";
const store = createSqliteStore("./sourcemaps.db");
Bun.serve({ port: 8081, fetch: createStoreHandler(store) });// next.config.mjs — build uploads to the remote store
import { withSourceMaps } from "@smapped-traces/nextjs";
import { createHttpStore } from "smapped-traces/store";
export default withSourceMaps(
{ /* your config */ },
{ store: () => createHttpStore("https://sourcemaps.internal") }
);// traces-handler.ts — resolves from the same remote store
import { createTracesHandler } from "smapped-traces/route";
import { createHttpStore } from "smapped-traces/store";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
const handler = createTracesHandler({
exporter: new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }),
store: createHttpStore("https://sourcemaps.internal"),
});npm install @smapped-traces/s3 @aws-sdk/client-s3import { S3Client } from "@aws-sdk/client-s3";
import { createS3Store } from "@smapped-traces/s3";
const store = createS3Store({
client: new S3Client({ region: "us-east-1" }),
bucket: "my-sourcemaps",
prefix: "sourcemaps/",
});
// Use with withSourceMaps(), createTracesHandler(), or createSourceMapResolver()The handler uses standard Web Request/Response, so it works outside Next.js:
// Bun
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { createTracesHandler } from "smapped-traces/route";
import { createSqliteStore } from "@smapped-traces/sqlite";
const handler = createTracesHandler({
exporter: new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }),
store: createSqliteStore("./sourcemaps.db"),
});
Bun.serve({ port: 8080, fetch: handler });| Path | Description |
|---|---|
smapped-traces/client |
SourceMappedSpanExporter — Client-side span exporter with debug ID enrichment |
smapped-traces/route |
createTracesHandler() — Request handler with source map resolution |
smapped-traces/resolve |
createSourceMapResolver() — Standalone source map resolver |
smapped-traces/store |
SourceMapStore, createHttpStore(), createStoreHandler() |
| Path | Description |
|---|---|
@smapped-traces/nextjs |
withSourceMaps() — Next.js config helper for build-time source map collection |
| Path | Description |
|---|---|
@smapped-traces/sqlite |
createSqliteStore() — SQLite-backed store |
| Path | Description |
|---|---|
@smapped-traces/s3 |
createS3Store() — S3-compatible bucket store |
The client exporter reads debug IDs from two global variables:
| Global | Bundler | Format |
|---|---|---|
globalThis._debugIds |
Turbopack | Stack trace keys → debug ID UUIDs |
globalThis.__DEBUG_IDS__ |
TC39 spec / webpack | URL keys → debug ID UUIDs |
Both are checked and merged automatically.
- Next.js 16+ (only for
@smapped-traces/nextjs— usesrunAfterProductionCompilehook) - OpenTelemetry SDK v2+
We review change proposals in Discussions before code. Start a Discussion and wait for a maintainer's review. If we accept the proposal, a Mathematic maintainer or agent will implement it and open the pull request. When Mathematic implements a proposal, the implementation PR will link to the Discussion and credit its original author. GitHub limits pull request creation to Mathematic maintainers and repository collaborators with write, maintain, or admin access, plus authorized maintenance agents. See CONTRIBUTING.md for the full process.
Apache-2.0
This project is free and open-source work by a 501(c)(3) non-profit. If you find it useful, please consider donating.