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/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+
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.