DEV Community

Mark
Mark

Posted on

Sentry Source Map Upload Debugging: From 0% Adoption to Full Resolution

After integrating Sentry error monitoring, Release Adoption stayed at 0%, and Source Maps never resolved.
Two days of debugging, six pitfalls cleared.

Background

The project is a Next.js 15 static export deployed on Cloudflare Pages. I wanted Sentry error monitoring so user errors show readable stack traces.

Seems simple: Install SDK → Upload Source Maps → Done.

Reality:

0% Adoption → SDK not initialized → Switch to CDN
Source Map upload fails → sentry-cli crashes → Switch to HTTP API
Wrong region → Token parse failure → Write parser logic
Release mismatch → Filename wrong → Unicode escape
Enter fullscreen mode Exit fullscreen mode

Pitfall 1: Release Adoption Stays at 0%

Symptoms

Sentry Dashboard:

  • Releases list shows the release ✅
  • But Adoption is 0% ❌
  • Events list is empty ❌

Root Cause

@sentry/browser got tree-shaken away by SWC.

// ❌ This code is eliminated by SWC during build
import * as Sentry from '@sentry/browser'
Sentry.init({ dsn: '...', release: process.env.NEXT_PUBLIC_SENTRY_RELEASE })
Enter fullscreen mode Exit fullscreen mode

@sentry/browser uses heavy lazy imports and conditional exports internally. SWC's minification pass treats these as dead code and removes them entirely.

Solution: Use Sentry CDN

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <script
          src="https://browser.sentry-cdn.com/7.113.0/bundle.min.js"
          crossorigin="anonymous"
        />
        <script dangerouslySetInnerHTML={{ __html: `
          Sentry.init({
            dsn: '${process.env.NEXT_PUBLIC_SENTRY_DSN}',
            release: '${process.env.NEXT_PUBLIC_SENTRY_RELEASE}',
            environment: 'production'
          })
        `}} />
      </head>
      <body>{children}</body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode

The CDN script lives outside SWC's processing pipeline and won't be tree-shaken.

Result: Release Adoption goes from 0% to >0% ✅

Pitfall 2: sentry-cli Crashes on GitHub Actions

Symptoms

- uses: actions/setup-node@v4
  with:
    node-version: 22
- run: npx @sentry/cli sourcemaps upload out/
Enter fullscreen mode Exit fullscreen mode

Runtime error:

Error: The module '/node_modules/@sentry/cli/bin/sentry-cli'
was compiled against a different Node.js version
Enter fullscreen mode Exit fullscreen mode

Root Cause

sentry-cli is a Rust-compiled binary with Node.js FFI bindings that are incompatible with the Actions Node.js version.

Solution: Direct HTTP API

// scripts/upload-sourcemaps.js
const fs = require('fs')
const https = require('https')

// Parse org ID and region from Auth Token
function parseSentryToken(authToken) {
  const payload = authToken.replace('sntrys_', '')
  const decoded = JSON.parse(Buffer.from(payload, 'base64').toString())
  return {
    orgId: decoded.org_slug,
    region: decoded.host === 'https://sentry.io' ? 'us' : 'eu'
  }
}

// multipart/form-data upload
function uploadSourceMap(options) {
  const boundary = '----FormBoundary' + Math.random().toString(36).slice(2)
  // ... build multipart body and send via https.request()
}
Enter fullscreen mode Exit fullscreen mode

Pitfall 3: Wrong Region

Symptoms

404 Not Found: /api/0/organizations/myorg/releases/
Enter fullscreen mode Exit fullscreen mode

Root Cause

Sentry has two regions: us.sentry.io and eu.sentry.io. My org is in US, but the default request went to EU.

Solution

The Auth Token encodes region info:

// Sentry token: sntrys_ prefix + base64(JSON)
const token = 'sntrys_eyJyZWdpb25FbmRwb2ludCI6ICJodHRwczovL3VzLnNlbnRyeS5pbyJ9'
const payload = Buffer.from(token.slice(7), 'base64').toString()
const info = JSON.parse(payload)
// info.regionEndpoint → "https://us.sentry.io"
Enter fullscreen mode Exit fullscreen mode

Pitfall 4: Release Name Mismatch

Symptoms

Source Maps upload succeeds, but Dashboard still shows "Missing."

Root Cause

The release name in Source Maps doesn't match what the SDK reports:

SDK reports:  "release": "utlkit-20260521-abc1234"
Source Map:   "release": "utlkit-abc1234"   missing date
Enter fullscreen mode Exit fullscreen mode

Solution

Generate the release name from one script, used by both SDK and upload:

// scripts/generate-sentry-release.js
const { execSync } = require('child_process')
const commitSha = execSync('git rev-parse --short HEAD').toString().trim()
const date = new Date().toISOString().split('T')[0]
const release = `utlkit-${date}-${commitSha}`
console.log(release)
Enter fullscreen mode Exit fullscreen mode

Pitfall 5: Unicode Escape Corrupts Filenames

Symptoms

Source Maps uploaded but Sentry shows garbled paths.

Root Cause

JSON.stringify converts non-ASCII characters to \uXXXX, which Sentry's API can't parse correctly.

Final Workflow

name: Upload Sentry Source Maps
on:
  push:
    branches: [main]

jobs:
  upload:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
      - run: npm ci
      - run: npm run build
      - run: node scripts/generate-sentry-release.js > .sentry-release
      - run: node scripts/upload-sourcemaps.js
        env:
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
          SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
Enter fullscreen mode Exit fullscreen mode

Summary

Pitfall Symptom Solution
SWC tree-shakes SDK Adoption 0% Use Sentry CDN
sentry-cli Node incompatibility Binary crash Direct HTTP API
Wrong region 404 Parse region from Token
Release mismatch Source Map Missing Unified release name
Unicode escape Garbled filenames Handle encoding properly
Dynamic import optimized away SDK not initialized CDN script

Core lesson: Sentry's official docs assume Node.js SSR or bundler plugins. The static export + CDN SDK combination requires custom upload logic.

Project

utlkit.com — All formatter tools run purely in the browser. The solutions above are running stably in production.

Top comments (0)