소스맵 업로드

빌드한 소스맵을 웹 버전별로 Sophonz에 올려, 프로덕션 에러의 스택 트레이스를 원본 파일과 줄 번호로 되돌립니다.

압축된 번들에서 난 에러는 onClick@chunks/36o8zuzlzx381.js:1:918처럼 보입니다. 같은 빌드의 소스맵을 올려 두면 로그와 트레이스 화면에서 onClick app/client-probes.tsx:43:19와 해당 줄의 코드로 바뀌어 보입니다. 소스맵은 웹 버전 단위로 보관되고, 스택 트레이스를 열 때 그 트레이스가 보고한 웹 버전의 소스맵으로 복원합니다.

웹 버전

웹 버전은 웹 빌드 자체의 버전입니다. Browser SDK(2.1.0 이상)는 init에 넘긴 appVersion을 리소스 속성 web.version으로 보내고, 소스맵은 이 값으로 매칭됩니다.

실행 환경service.versionweb.version
일반 브라우저appVersionappVersion
네이티브 앱의 WebView네이티브 앱 버전 (브릿지가 덮어씀)페이지에 설정한 appVersion

WebView에서는 네이티브 브릿지가 service.version을 앱 버전으로 바꾸지만 web.version은 페이지가 설정한 값을 유지합니다. 웹 배포 주기가 앱보다 빨라도 웹 빌드마다 소스맵을 따로 올리고 찾을 수 있는 이유입니다. 자세한 내용은 WebView 연동을 참고하세요.

CAUTION — 업로드 버전과 SDK 버전은 같은 값이어야 합니다

소스맵은 그 빌드의 스크립트와만 맞습니다. CI에서 하나의 변수로 SDK의 appVersion과 업로드의 웹 버전을 함께 설정하세요. 값이 어긋나면 업로드는 성공해도 스택 트레이스는 복원되지 않습니다.

업로드 방법은 두 가지입니다.

  • @sophonz/cli: 번들러에 상관없이 빌드 결과 디렉터리를 올립니다. Next.js, webpack, Rollup, esbuild 등.
  • @sophonz/vite-sourcemap: Vite 빌드가 끝나면 자동으로 올립니다.

@sophonz/cli

NPM Version

설치

npm install @sophonz/cli --save-dev

Node.js 20 이상이 필요합니다. 설치 없이 npx @sophonz/cli로 실행해도 됩니다.

사용법

export SOPHONZ_TOKEN=sophonz_pat_...
 
npx @sophonz/cli upload-sourcemaps \
  --path dist \
  --web-version 2026.09.15-1

Next.js는 스크립트가 /_next/static 아래에서 제공되므로 접두사를 지정합니다. next.config에서 productionBrowserSourceMaps: true로 소스맵을 만들어야 합니다.

npx @sophonz/cli upload-sourcemaps \
  --path .next/static \
  --url-prefix _next/static \
  --web-version "$WEB_VERSION"
옵션환경 변수설명
-p, --path—소스맵을 찾을 디렉터리 (필수)
-w, --web-version—웹 버전. SDK의 appVersion과 같은 값 (필수)
--url-prefix—스크립트가 제공되는 URL 경로. 예: _next/static
-t, --tokenSOPHONZ_TOKEN앱 액세스 토큰
--api-urlSOPHONZ_API_URL기본값 https://app.sophonz.ai
--dry-run—업로드하지 않고 대상만 출력

스크립트와 소스맵 짝짓기

CLI는 각 스크립트 끝의 //# sourceMappingURL= 주석을 읽어, 소스맵을 그 스크립트의 경로로 업로드합니다. 스택 트레이스에는 스크립트 이름만 남기 때문입니다.

번들러가 소스맵 이름을 스크립트와 다르게 짓는 경우도 이 방식으로 맞춰집니다. Next.js 16(Turbopack)은 36o8zuzlzx381.js의 소스맵을 1-1m44t1taogc.js.map으로 만듭니다. 인라인(data:) 소스맵도 추출해서 올리며, 어떤 스크립트도 참조하지 않는 .js.map은 자기 이름으로 올립니다.

--dry-run으로 짝이 어떻게 정해졌는지 확인할 수 있습니다.

[sophonz] 8 source map(s) for web version 1.4.0 → https://app.sophonz.ai
  (dry run) _next/static/chunks/36o8zuzlzx381.js.map ← chunks/1-1m44t1taogc.js.map

@sophonz/vite-sourcemap 플러그인

NPM Version

주요 기능

  • Vite 빌드 완료 후 소스맵 자동 업로드
  • 포함/제외 패턴으로 파일 필터링
  • gzip 압축 지원
  • 모든 소스맵을 tar.gz 하나로 묶어 한 번에 업로드하는 번들 모드
  • 동시 업로드 제한 및 재시도
  • CI 환경 감지, 업로드 후 로컬 소스맵 삭제
  • 커스텀 경로 변환 및 URL 접두사 지원

설치

npm install @sophonz/vite-sourcemap --save-dev

기본 설정

// vite.config.ts
import { defineConfig } from 'vite'
import sourcemapUpload from '@sophonz/vite-sourcemap'
 
export default defineConfig({
  build: {
    sourcemap: true, // 중요: 소스맵 생성을 활성화해야 합니다
  },
  plugins: [
    sourcemapUpload({
      endpoint: 'https://app.sophonz.ai/api/v1/web-sourcemaps',
      apiKey: process.env.SOPHONZ_TOKEN,
      // 웹 버전. SDK init의 appVersion과 같은 값
      appVersion: process.env.WEB_VERSION,
    }),
  ],
})

NOTE — apiKey에 넣을 값

apiKey에는 앱 단위로 발급한 Sophonz 액세스 토큰을 넣습니다. 발급 방법은 액세스 토큰 발급을 참고하세요.

고급 설정

// vite.config.ts
import { defineConfig } from 'vite'
import sourcemapUpload from '@sophonz/vite-sourcemap'
 
export default defineConfig({
  build: {
    sourcemap: true,
  },
  plugins: [
    sourcemapUpload({
      endpoint: 'https://app.sophonz.ai/api/v1/web-sourcemaps',
      apiKey: process.env.SOPHONZ_TOKEN,
 
      // 웹 버전. 소스맵은 이 값으로 보관됩니다
      appVersion: process.env.WEB_VERSION,
      build: process.env.GIT_SHA,
 
      // 파일 필터링 (출력 디렉터리 기준 상대 경로에 적용)
      include: [/assets\/.+\.js\.map$/],
      exclude: [/node_modules/],
 
      // 스크립트가 제공되는 경로가 출력 디렉터리 구조와 다를 때
      urlPrefix: 'static/js',
 
      // 업로드 설정
      concurrency: 3,
      retries: 5,
      gzip: true,
      deleteAfterUpload: true,
 
      // CI에서만 실행
      ciOnly: true,
    }),
  ],
})

소스맵 개수가 많아 요청 수를 줄이고 싶다면 번들 모드를 사용합니다. 모든 소스맵을 tar.gz 하나로 묶어 한 번의 요청으로 업로드합니다.

sourcemapUpload({
  endpoint: 'https://app.sophonz.ai/api/v1/web-sourcemaps',
  apiKey: process.env.SOPHONZ_TOKEN,
  appVersion: process.env.WEB_VERSION,
  bundle: true,
  bundleFilename: 'sourcemaps.tar.gz',
})

설정 옵션

필수 옵션

옵션타입설명
endpointstring소스맵을 업로드할 API 엔드포인트

선택 옵션

옵션타입기본값설명
apiKeystring-액세스 토큰. Authorization: Bearer <token> 헤더로 전송됩니다
appVersionstring-웹 버전. 소스맵이 이 값으로 보관·매칭됩니다
releasestring-appVersion이 없을 때 웹 버전으로 쓰입니다
buildstring-빌드 번호 또는 커밋 SHA. 빌드 ID로 기록됩니다
method'POST' | 'PUT''POST'HTTP 메서드. Sophonz 엔드포인트는 POST만 받습니다
headersRecord<string, string>{}추가 HTTP 헤더
appType'ANDROID' | 'IOS' | 'WEB' | 'ALL' | nullnull플랫폼 구분. 현재 엔드포인트는 사용하지 않습니다
fileFieldNamestring'file'파일 업로드용 폼 필드명. Sophonz 엔드포인트는 file을 요구합니다
fieldsRecord<string, string | number | boolean>{}추가 폼 필드
include(string | RegExp)[]-포함할 파일 패턴. 문자열은 glob이 아니라 정규식 소스로 해석됩니다
exclude(string | RegExp)[]-제외할 파일 패턴
urlPrefixstring-논리 경로(name·path 필드) 앞에 붙일 접두사
transformPath(path: string) => string-논리 경로 변환 함수. 지정하면 urlPrefix는 무시됩니다
concurrencynumber6동시 업로드 수
retriesnumber3업로드 중 예외가 발생했을 때의 재시도 횟수 (지수 백오프)
gzipbooleantrue업로드 전 gzip 압축
bundlebooleanfalse모든 소스맵을 tar.gz 하나로 묶어 한 번에 업로드
bundleFilenamestring'sourcemaps.tar.gz'번들 모드에서 사용할 아카이브 파일명
deleteAfterUploadbooleanfalse업로드 성공 후 로컬 .map 파일 삭제
ciOnlybooleanfalseCI 환경에서만 실행
verbosebooleanCI에서 true, 로컬에서 false상세 로그 출력

CAUTION — 업로드 실패는 빌드를 멈추지 않습니다

서버가 오류 응답을 반환하면 플러그인은 로그만 남기고 빌드를 계속 진행합니다. 업로드를 빌드 성공 조건으로 두려면 @sophonz/cli를 별도 단계로 실행하세요. CLI는 실패하면 종료 코드 1로 끝납니다.

NOTE — Vite가 아닌 번들러의 소스맵 이름

플러그인은 .map 파일의 출력 경로를 그대로 논리 경로로 보냅니다. Vite처럼 소스맵 이름이 스크립트 이름 뒤에 .map만 붙는 번들러에서는 그대로 맞습니다. 이름이 다른 번들러는 @sophonz/cli를 사용하세요.

업로드 엔드포인트

POST https://app.sophonz.ai/api/v1/web-sourcemaps
Authorization: Bearer sophonz_pat_...
Content-Type: multipart/form-data

https://app.sophonz.com/api/v1/web-sourcemaps도 같은 엔드포인트를 서비스합니다.

요청 필드

필드설명
file소스맵 파일. contentEncoding=gzip이면 gzip 압축된 내용
path, name논리 경로. 스택 트레이스의 스크립트 URL과 이 경로의 끝부분을 비교합니다
webVersion웹 버전
appVersionwebVersion이 없을 때의 웹 버전
release둘 다 없을 때의 웹 버전. 셋 다 없으면 unreleased
build빌드 ID
contentEncodinggzip 압축했을 때 gzip
bundle, bundleFormat, fileCount번들 모드일 때만

요청에는 어느 앱의 소스맵인지 가리키는 값이 없습니다. 이 점이 아래 토큰 정책의 근거입니다.

응답

업로드가 성공하면 201과 함께 저장 결과를 돌려줍니다.

{
  "ok": true,
  "key": "sourcemaps/<orgId>/<projectId>/<appId>/web/1.4.0/assets/app.js.map",
  "app": "next-sample",
  "serviceNamespace": "sophonz",
  "release": "unreleased",
  "version": "1.4.0",
  "bundle": false,
  "fileCount": null
}

key는 sourcemaps/<조직 id>/<프로젝트 id>/<앱 id>/web/<웹 버전>/<논리 경로> 형태입니다. 같은 웹 버전에 같은 경로를 다시 올리면 덮어씁니다.

제한과 오류

  • 업로드 한 건은 200MB까지 허용됩니다. 번들 모드에서는 압축된 아카이브 전체가 이 한도에 걸립니다.
  • 소스맵은 비공개로 저장됩니다. 소스맵에는 원본 소스가 그대로 들어 있으므로 공개 저장소나 CDN에 함께 배포하지 마세요.
상태원인
401토큰이 없거나, 잘못됐거나, 만료·폐기됨
400multipart 요청이 아니거나 file 필드가 없거나 파일이 비어 있음
413파일이 200MB를 초과
500서버가 소스맵을 저장하지 못함

액세스 토큰 발급

소스맵 업로드에는 Sophonz 액세스 토큰이 필요합니다. 토큰은 앱 단위로만 발급됩니다. 업로드 요청에는 앱을 가리키는 값이 없고, 소스맵이 어느 앱에 속하는지는 토큰이 정하기 때문입니다.

콘솔에서 발급

  1. https://app.sophonz.ai에 로그인
  2. 설정을 엽니다
  3. 대상 프로젝트와 앱을 선택합니다
  4. 액세스 토큰 섹션에서 토큰 이름과 만료 기간(만료 없음 / 30일 / 90일 / 1년)을 정해 생성합니다
  5. 토큰은 이때 한 번만 표시됩니다. 이 화면에서 복사하세요

발급된 토큰은 sophonz_pat_<임의 문자열> 형식입니다.

CAUTION — 토큰은 한 번만 표시됩니다

생성 화면을 벗어나면 토큰 원문은 다시 볼 수 없습니다. 목록에는 마지막 네 자리 힌트와 마지막 사용 시각, 만료일만 남습니다. CI/CD에서는 시크릿으로 관리하세요.

폐기는 즉시 적용되며 되돌릴 수 없습니다. 교체할 때는 새 토큰을 먼저 발급해 CI에 반영한 뒤 이전 토큰을 폐기하세요.

업로드 확인

설정 > 프로젝트 > 앱 > 매핑 파일에 앱의 버전 목록이 있습니다. 목록은 텔레메트리에서 자동으로 채워지며, 웹 버전마다 올라온 소스맵 개수가 표시됩니다.

  • 텔레메트리를 보냈는데 소스맵이 없는 웹 버전에는 경고가 표시됩니다. 스택 트레이스가 복원되지 않는 버전입니다.
  • 출시 전에 올린 웹 버전은 텔레메트리가 오기 전에도 목록에 나타납니다.

에러가 들어오면 로그나 트레이스에서 해당 행을 펼칩니다. 스택 트레이스 뷰어가 원본 파일, 줄 번호와 코드 한 줄을 함께 보여 줍니다. 소스맵이 없으면 "버전 X의 매핑 파일 없음"이 표시됩니다.

플랫폼별 소스맵 (appType)

appType 옵션은 'ANDROID' | 'IOS' | 'WEB' | 'ALL' | null을 받아 폼 필드로 함께 전송되지만, 현재 엔드포인트는 이 값을 사용하지 않습니다. 소스맵은 앱과 웹 버전 기준으로만 보관됩니다.

같은 앱에서 플랫폼별로 다른 번들을 배포한다면 웹 버전을 다르게 지정하세요. 예를 들어 2026.09.15-1-android처럼 구분하고, 각 번들의 SDK appVersion도 같은 값으로 맞춥니다.

GitHub Actions와 함께 사용

CLI를 별도 단계로 실행하는 예입니다. 웹 버전 변수 하나를 빌드와 업로드에 함께 넘깁니다.

# .github/workflows/deploy.yml
name: Deploy
 
on:
  push:
    branches: [main]
 
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      WEB_VERSION: ${{ github.run_number }}-${{ github.sha }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - name: Build
        # 앱 코드에서 이 값을 Sophonz.init({ appVersion })으로 넘깁니다
        env:
          NEXT_PUBLIC_WEB_VERSION: ${{ env.WEB_VERSION }}
        run: npm run build
      - name: Upload source maps
        env:
          SOPHONZ_TOKEN: ${{ secrets.SOPHONZ_TOKEN }}
        run: npx @sophonz/cli upload-sourcemaps --path .next/static --url-prefix _next/static --web-version "$WEB_VERSION"

Vite 플러그인을 쓴다면 업로드 단계 없이 npm run build에 SOPHONZ_TOKEN과 WEB_VERSION을 넘기면 됩니다.

CI 환경 감지

Vite 플러그인은 다음 환경 변수 중 하나라도 설정돼 있으면 CI 환경으로 판단합니다. 이 판정은 ciOnly와 verbose의 기본값에 쓰입니다.

  • CI
  • GITHUB_ACTIONS
  • GITLAB_CI
  • BUILDKITE
  • CIRCLECI
  • TRAVIS
  • BITBUCKET_BUILD_NUMBER