# Source maps

> Source: https://cross-deck.com/docs/source-maps/ · Crossdeck developer docs (generated from the published page).

Errors USP

# Source maps

Production JavaScript is minified — every error reads as `main.a1b2c3.js:1:48202`. Upload your source maps to Crossdeck and every stack frame resolves to readable references like `src/checkout/Pay.tsx:114 — handleSubmit`.

Prefer to let an AI assistant do the setup instead? [AI-assisted setup →](https://cross-deck.com/docs/source-maps/ai-setup/index.html)

## TL;DR

- Install `@cross-deck/cli` as a dev dependency.

- Make sure your bundler emits external source maps with `sourcesContent` inlined.

- After every production build, run `npx @cross-deck/cli upload-sourcemaps --release "$GIT_SHA" --url-prefix ./dist`.

- Stamp the same release identifier on every Crossdeck event by setting `appVersion: process.env. _GIT_SHA` in your SDK init.

- Trigger a test error and confirm the dashboard renders readable file paths.

## What is this?

Your bundler — Vite, Webpack, Next.js, Rollup, ESBuild, Parcel — collapses your TypeScript / JSX into a handful of minified `.js` files for production. Variable names become `a`, functions become `o`, file paths become `main. .js`. Browsers run this squashed code fine, but when something throws, the stack trace is unreadable.

A **source map** is the dictionary that translates squashed back into English. Modern bundlers produce one as part of the build. Crossdeck's CLI ships your maps into private cloud storage so our backend can use them when errors arrive, never serving the raw map to clients.

Privacy posture. Source maps reveal original source code — function names, variable
names, file paths, comments. Crossdeck stores them in private Cloud Storage with the same access
posture as your customer database. Only post-decode resolved frames leave the backend; the
raw map content is never exposed to the dashboard or any client.

## Prerequisites

- A Crossdeck project with at least one app registered (web).

- A Crossdeck **secret** key (`cd_sk_live_…`). Grab one from [Developers → API keys](https://cross-deck.com/dashboard/developers/api/). Publishable keys (`cd_pub_…`) cannot upload sourcemaps — they're client-only.

- Node.js 18 or later in your build environment.

- A bundler that emits external `.js.map` files with `sourcesContent` inlined.

## Install the CLI

```
npm install --save-dev @cross-deck/cli
```

Or pnpm / yarn / bun — same package name, same surface.

## Configure your bundler

Source maps need to be emitted as *external* `.js.map` files alongside each `.js` bundle, with `sourcesContent` embedded so we have the original source to display.

### Vite / Rollup

```
// vite.config.ts
export default defineConfig({
  build: { sourcemap: true },
});
```

### Webpack

```
// webpack.config.js
module.exports = {
  devtool: "source-map",
};
```

Avoid `eval-source-map` and `inline-source-map` for production — those embed the map in the bundle and can't be uploaded separately.

### Next.js

```
// next.config.js
module.exports = {
  productionBrowserSourceMaps: true,
};
```

### ESBuild

```
// esbuild.config.js
await esbuild.build({
  entryPoints: ["src/index.ts"],
  bundle: true,
  outdir: "dist",
  sourcemap: true,
  sourcesContent: true,
});
```

## Upload from CI

Run this command from your build pipeline after the production build finishes:

```
npx @cross-deck/cli upload-sourcemaps \
  --release "$GIT_SHA" \
  --url-prefix "https://your-app.com/static/js/" \
  ./dist
```

The CLI walks `./dist` recursively, pairs every `.js` with its companion `.map`, and uploads in batches of ≤100 files per request. The `--url-prefix` is the URL your bundles are served from at runtime — without the bundle filename itself. Examples:

- **Vercel + Next.js:** usually `https://your-app.vercel.app/_next/`. Source map output dir is `.next/static`.

- **Vite + Netlify:** usually `https://your-site.netlify.app/assets/`. Source map output dir is `dist/assets`.

- **Custom domain on Firebase Hosting:** `https://your-domain.com/` (path is just the hosting public dir from `firebase.json`).

### Setting CROSSDECK_AUTH_TOKEN

The CLI authenticates with your project's secret key. Store it in your CI secrets, never commit it:

- **GitHub Actions:** repo settings → Secrets and variables → Actions → new secret. Then reference as `${{ secrets.CROSSDECK_AUTH_TOKEN }}`.

- **Vercel:** project settings → Environment Variables → mark as *Sensitive*.

- **Netlify:** site settings → Environment variables → new secret.

- **CircleCI / GitLab CI:** add as a masked environment variable.

### Example: GitHub Actions

```
name: Deploy
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npm run build
      - name: Upload source maps to Crossdeck
        env:
          CROSSDECK_AUTH_TOKEN: ${{ secrets.CROSSDECK_AUTH_TOKEN }}
        run: |
          npx @cross-deck/cli upload-sourcemaps \
            --release "$GITHUB_SHA" \
            --url-prefix "https://your-app.com/static/js/" \
            ./dist
      - name: Strip maps from deploy artefact
        run: find dist -name '*.map' -delete
```

### Strip the maps from production

Source maps should NEVER be deployed publicly — they leak your original source. After upload to Crossdeck, delete them from the deploy artefact. The `find` command in the example above does this. Alternatively, use your deploy target's exclude config (`.vercelignore`, etc.).

## Verify it worked

- Deploy your app with the upload step in your pipeline.

- Trigger a test error from the deployed app: 

```
throw new Error("crossdeck sourcemap test");
```

- Open [Errors → Issues](https://cross-deck.com/dashboard/errors/) in the dashboard. The most-recent issue should show a top frame like `src/ .tsx: — ` instead of the minified hash.

## Framework recipes

### Next.js (App Router) on Vercel

```
// next.config.js
module.exports = {
  productionBrowserSourceMaps: true,
};

// app/layout.tsx
import { Crossdeck } from "@cross-deck/web";
Crossdeck.init({
  appId: process.env.NEXT_PUBLIC_CROSSDECK_APP_ID,
  publicKey: process.env.NEXT_PUBLIC_CROSSDECK_KEY,
  environment: "production",
  appVersion: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
});

// .github/workflows/deploy.yml — after `vercel build`:
npx @cross-deck/cli upload-sourcemaps \
  --release "$VERCEL_GIT_COMMIT_SHA" \
  --url-prefix "https://$VERCEL_URL/_next/" \
  ./.next/static
```

### Vite + Netlify

```
// vite.config.ts
export default defineConfig({
  build: { sourcemap: true },
  define: { __APP_SHA__: JSON.stringify(process.env.COMMIT_REF) },
});

// src/main.ts
Crossdeck.init({
  appId, publicKey, environment: "production",
  appVersion: __APP_SHA__,
});

// netlify.toml — postbuild hook:
[build]
  command = "npm run build && npx @cross-deck/cli upload-sourcemaps --release $COMMIT_REF --url-prefix https://$URL/assets/ ./dist && find dist -name '*.map' -delete"
```

### Webpack + custom server

```
// webpack.config.js
module.exports = {
  devtool: "source-map",
};

// In your CI (postbuild):
npx @cross-deck/cli upload-sourcemaps \
  --release "$CI_COMMIT_SHA" \
  --url-prefix "https://your-domain.com/static/js/" \
  ./dist/static/js
find ./dist -name '*.map' -delete
```

## Troubleshooting

### "Source maps not uploaded for this release"

The dashboard shows this card when no sourcemap covers any frame's bundle URL for the event's release. Most common causes:

- The `--url-prefix` doesn't match the runtime URL of your bundles. Compare the `filename` field on a real error event in the dashboard against the prefix you passed.

- The `--release` value passed to the CLI doesn't match the `appVersion` the SDK stamps on events. Both should be the git SHA (or whatever stable release identifier you use).

- The CLI ran but no `.map` files were discovered — check `npx @cross-deck/cli upload-sourcemaps --verbose …` for the file list it scanned.

### "Source map is missing sourcesContent "

Your bundler emitted a map without inline source content. The CLI rejects these because Crossdeck can't display the original source without fetching the raw files at decode time, which is too slow for the dashboard. Fix:

- Vite / Rollup: set `build.sourcemap: true` (NOT `"hidden"` and not just `true` on the Rollup output options — the Vite higher-level toggle handles it).

- Webpack: `devtool: 'source-map'` (NOT `'nosources-source-map'`).

- ESBuild: `sourcesContent: true`.

### Stack frames resolve for SOME events but not others

The customer's app probably has multiple bundles (your code + vendor chunks), and you've only uploaded maps for some. The CLI uploads every `.js + .map` pair it finds, so re-running with `--verbose` against the full `dist/` directory should pick up the missing ones.

### "Authentication failed"

The CLI accepts **secret keys only** (`cd_sk_…`). Publishable keys (`cd_pub_…`) are rejected. Get a secret key from [Developers → API keys](https://cross-deck.com/dashboard/developers/api/) in the dashboard, store it in CI secrets as `CROSSDECK_AUTH_TOKEN`.

CLI source is public at [github.com/Crossdeckhq/crossdeck-cli](https://github.com/Crossdeckhq/crossdeck-cli). File an issue there, or email [support@cross-deck.com](mailto:support@cross-deck.com).
