Crossdeck Docs

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 →

TL;DR

  1. Install @cross-deck/cli as a dev dependency.
  2. Make sure your bundler emits external source maps with sourcesContent inlined.
  3. After every production build, run npx @cross-deck/cli upload-sourcemaps --release "$GIT_SHA" --url-prefix <your-bundle-url-prefix> ./dist.
  4. Stamp the same release identifier on every Crossdeck event by setting appVersion: process.env.<FRAMEWORK_PREFIX>_GIT_SHA in your SDK init.
  5. 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.<hash>.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. 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

  1. Deploy your app with the upload step in your pipeline.
  2. Trigger a test error from the deployed app:
    throw new Error("crossdeck sourcemap test");
  3. Open Errors → Issues in the dashboard. The most-recent issue should show a top frame like src/<file>.tsx:<line> — <function> 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 in the dashboard, store it in CI secrets as CROSSDECK_AUTH_TOKEN.


CLI source is public at github.com/VistaApps-za/crossdeck-cli. File an issue there, or email support@cross-deck.com.