소스맵 업로드
Vite 플러그인을 사용해 빌드 후 소스맵을 Sophonz 서버에 자동 업로드하고 프로덕션 에러의 원본 소스 위치를 추적합니다.
@sophonz/vite-sourcemap 플러그인을 사용하면 Vite 빌드 완료 후 소스맵을 자동으로 Sophonz 서버에 업로드할 수 있습니다. 이를 통해 프로덕션 환경에서 발생한 에러의 원본 소스코드 위치를 정확하게 추적할 수 있습니다.
@sophonz/vite-sourcemap 플러그인
주요 기능
- 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_ACCESS_TOKEN,
release: process.env.GIT_SHA,
}),
],
})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_ACCESS_TOKEN,
// 릴리즈 정보. release는 저장 경로에 포함됩니다
release: process.env.GIT_SHA,
appVersion: process.env.npm_package_version,
build: process.env.BUILD_NUMBER,
// 파일 필터링 (출력 디렉터리 기준 상대 경로에 적용)
include: [/assets\/.+\.js\.map$/],
exclude: [/node_modules/],
// 논리 경로 앞에 붙일 접두사 (CDN 경로 등)
urlPrefix: '~/static/js',
// 업로드 설정
concurrency: 3,
retries: 5,
gzip: true,
deleteAfterUpload: true,
// CI에서만 실행
ciOnly: true,
// 추가 헤더
headers: {
'X-Custom-Header': 'value',
},
// 추가 폼 필드
fields: {
environment: 'production',
team: 'frontend',
},
}),
],
})소스맵 개수가 많아 요청 수를 줄이고 싶다면 번들 모드를 사용합니다. 모든 소스맵을 tar.gz 하나로 묶어 한 번의 요청으로 업로드합니다.
sourcemapUpload({
endpoint: 'https://app.sophonz.ai/api/v1/web-sourcemaps',
apiKey: process.env.SOPHONZ_ACCESS_TOKEN,
release: process.env.GIT_SHA,
bundle: true,
bundleFilename: 'sourcemaps.tar.gz',
})설정 옵션
필수 옵션
| 옵션 | 타입 | 설명 |
|---|---|---|
endpoint | string | 소스맵을 업로드할 API 엔드포인트 |
선택 옵션
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
method | 'POST' | 'PUT' | 'POST' | HTTP 메서드. Sophonz 엔드포인트는 POST만 받습니다 |
headers | Record<string, string> | {} | 추가 HTTP 헤더 |
apiKey | string | - | 액세스 토큰. Authorization: Bearer <token> 헤더로 전송됩니다 |
release | string | - | 릴리즈 식별자 (예: git SHA). 저장 경로에 포함됩니다 |
appVersion | string | - | 앱 버전 (semver). 폼 필드로 전송됩니다 |
build | string | - | 빌드 번호 또는 CI 빌드 ID. 폼 필드로 전송됩니다 |
appType | 'ANDROID' | 'IOS' | 'WEB' | 'ALL' | null | null | 플랫폼 구분. 현재 엔드포인트는 사용하지 않습니다 |
fileFieldName | string | 'file' | 파일 업로드용 폼 필드명. Sophonz 엔드포인트는 file을 요구합니다 |
fields | Record<string, string | number | boolean> | {} | 추가 폼 필드 |
include | (string | RegExp)[] | - | 포함할 파일 패턴. 문자열은 glob이 아니라 정규식 소스로 해석됩니다 |
exclude | (string | RegExp)[] | - | 제외할 파일 패턴. 판정 기준은 include와 같습니다 |
urlPrefix | string | - | 논리 경로(name·path 필드) 앞에 붙일 접두사 |
transformPath | (path: string) => string | - | 논리 경로 변환 함수. 지정하면 urlPrefix는 무시됩니다 |
concurrency | number | 6 | 동시 업로드 수 |
retries | number | 3 | 업로드 중 예외가 발생했을 때의 재시도 횟수 (지수 백오프) |
gzip | boolean | true | 업로드 전 gzip 압축. 파일명 뒤에 .gz가 붙고 contentEncoding=gzip이 함께 전송됩니다 |
bundle | boolean | false | 모든 소스맵을 tar.gz 하나로 묶어 한 번에 업로드. 활성화하면 concurrency와 파일별 gzip은 적용되지 않습니다 |
bundleFilename | string | 'sourcemaps.tar.gz' | 번들 모드에서 사용할 아카이브 파일명 |
deleteAfterUpload | boolean | false | 업로드 성공 후 로컬 .map 파일 삭제 |
ciOnly | boolean | false | CI 환경에서만 실행 |
verbose | boolean | CI에서 true, 로컬에서 false | 상세 로그 출력 |
CAUTION — 업로드 실패는 빌드를 멈추지 않습니다
서버가 오류 응답을 반환하면 플러그인은 로그만 남기고 빌드를 계속 진행합니다. 업로드 여부는 verbose 로그나 아래 응답으로 확인하세요.
업로드 엔드포인트
POST https://app.sophonz.ai/api/v1/web-sourcemaps
Authorization: Bearer sophonz_pat_...
Content-Type: multipart/form-datahttps://app.sophonz.com/api/v1/web-sourcemaps도 같은 엔드포인트를 서비스합니다. 별도의 API 전용 호스트는 없습니다.
요청 필드
플러그인이 요청마다 보내는 값입니다.
| 필드 | 설명 |
|---|---|
file | 소스맵 파일. gzip: true면 gzip 압축된 내용이며 파일명 뒤에 .gz가 붙습니다 |
path, name | 논리 경로. urlPrefix 또는 transformPath를 적용한 결과입니다 |
release | 릴리즈 식별자. 비어 있으면 unreleased로 저장됩니다 |
contentEncoding | gzip 압축했을 때만 gzip으로 전송됩니다 |
bundle, bundleFormat, fileCount | 번들 모드일 때만 전송됩니다 |
appVersion, build, appType, fields | 함께 전송되지만 현재 저장 위치에는 영향을 주지 않습니다 |
요청에는 어느 앱의 소스맵인지 가리키는 값이 없습니다. 이 점이 아래 토큰 정책의 근거입니다.
응답
업로드가 성공하면 201과 함께 저장 결과를 돌려줍니다.
{
"ok": true,
"key": "sourcemaps/<orgId>/<appId>/<release>/assets/app.js.map",
"app": "Nextjs Sample",
"serviceNamespace": "next-sample",
"release": "abc1234",
"bundle": false,
"fileCount": null
}key는 sourcemaps/<조직 id>/<앱 id>/<release>/<논리 경로> 형태입니다. 조직 id와 앱 id는 정수가 아니라 cuid 문자열이며(예: cmtpu66780001018vem9ri6vs), 토큰에서 결정되므로 요청에 넣을 수 없습니다.
제한과 오류
- 업로드 한 건은 200MB까지 허용됩니다. 번들 모드에서는 압축된 아카이브 전체가 이 한도에 걸립니다.
- 소스맵은 비공개로 저장되며 외부에 공개되지 않습니다. 소스맵에는 원본 소스가 그대로 들어 있으므로 공개 저장소나 CDN에 함께 배포하지 마세요.
| 상태 | 원인 |
|---|---|
401 | 토큰이 없거나, 잘못됐거나, 만료·폐기됨 |
400 | multipart 요청이 아니거나 file 필드가 없거나 파일이 비어 있음 |
413 | 파일이 200MB를 초과 |
500 | 서버가 소스맵을 저장하지 못함 |
액세스 토큰 발급
소스맵 업로드에는 Sophonz 액세스 토큰이 필요합니다. 토큰은 앱 단위로만 발급됩니다. 프로젝트 단위 토큰은 없습니다.
임의의 제약이 아니라 업로드 요청의 형태에서 오는 결과입니다. 플러그인이 보내는 것은 파일과 논리 경로, 릴리즈 정보뿐이고 앱을 가리키는 값이 없습니다. 소스맵이 어느 앱에 속하는지 서버에 알려주는 것은 토큰 하나뿐이므로, 토큰이 앱 하나를 가리키지 않으면 저장 위치를 정할 수 없습니다.
콘솔에서 발급
https://app.sophonz.ai에 로그인- 조직 설정을 엽니다
- 대상 프로젝트를 선택합니다
- 각 앱 아래의 액세스 토큰 섹션을 엽니다
- 토큰 이름을 입력하고 만료 기간(만료 없음 / 30일 / 90일 / 1년)을 선택한 뒤 생성합니다
- 토큰은 이때 한 번만 표시됩니다. 이 화면에서 복사하세요
발급된 토큰은 sophonz_pat_<임의 문자열> 형식입니다.
CAUTION — 토큰은 한 번만 표시됩니다
생성 화면을 벗어나면 토큰 원문은 어디에서도 다시 볼 수 없습니다. 목록에는 마지막 네 자리 힌트와 마지막 사용 시각, 만료일만 남습니다. 분실했다면 새로 발급받아야 합니다. CI/CD에서는 시크릿으로 관리하세요.
폐기는 즉시 적용되며 되돌릴 수 없습니다. 폐기한 토큰을 쓰던 CI는 다음 빌드부터 401로 실패하므로, 교체할 때는 새 토큰을 먼저 발급해 CI에 반영한 뒤 이전 토큰을 폐기하세요. 목록의 마지막 사용 시각이 어떤 토큰을 정리해도 되는지 판단하는 기준이 됩니다.
토큰을 발급하는 공개 REST API는 없습니다. 발급과 폐기는 콘솔에서만 할 수 있습니다.
플랫폼별 소스맵 (appType)
appType 옵션은 'ANDROID' | 'IOS' | 'WEB' | 'ALL' | null을 받아 폼 필드로 함께 전송됩니다. 다만 현재 /api/v1/web-sourcemaps는 이 값을 저장 위치나 조회에 사용하지 않습니다. 소스맵은 앱과 릴리즈 기준으로만 저장되며, 구버전 서버로 업로드하던 빌드와의 호환을 위해 필드만 남아 있습니다.
같은 앱에서 플랫폼별로 다른 번들을 배포한다면 appType 대신 release를 다르게 지정하세요. 저장 경로에 릴리즈가 포함되므로, 릴리즈가 같고 파일 경로가 같으면 나중에 올라온 소스맵이 앞의 것을 덮어씁니다.
GitHub Actions와 함께 사용
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Build and upload sourcemaps
env:
SOPHONZ_ACCESS_TOKEN: ${{ secrets.SOPHONZ_ACCESS_TOKEN }}
GIT_SHA: ${{ github.sha }}
run: npm run buildSOPHONZ_ACCESS_TOKEN과 GIT_SHA는 플러그인이 정해 둔 이름이 아니라 위 vite.config.ts가 process.env에서 읽는 이름입니다. 다른 이름을 쓰려면 양쪽을 함께 바꾸세요. ciOnly: true를 설정했다면 GitHub Actions가 자동으로 설정하는 CI·GITHUB_ACTIONS 덕분에 CI에서만 업로드가 실행됩니다.
CI 환경 감지
플러그인은 다음 환경 변수 중 하나라도 설정돼 있으면 CI 환경으로 판단합니다.
CIGITHUB_ACTIONSGITLAB_CIBUILDKITECIRCLECITRAVISBITBUCKET_BUILD_NUMBER
이 판정은 ciOnly가 업로드를 실행할지, verbose의 기본값이 무엇인지를 결정합니다.
TIP — 소스맵 업로드 확인
verbose: true를 지정하면 업로드 과정을 상세하게 확인할 수 있습니다. 업로드가 끝나면 대시보드에서 프로덕션 에러의 원본 소스 코드 위치를 확인할 수 있습니다.