React 18 + Vite 프로젝트에 Sentry를 도입하면서, 기본 설정 외에도 환경 분리 전략과 pnpm 특유의 의존성 격리 구조 때문에 신경 써야 할 부분이 꽤 있었다. 처음 도입하는 분들이 같은 곳에서 헤매지 않도록 개념부터 실전 설정까지 순서대로 정리해본다.
Sentry란 무엇이고, 왜 쓸까?
Sentry는 애플리케이션에서 발생하는 에러와 성능 이슈를 실시간으로 수집·추적해주는 모니터링 플랫폼이다. 브라우저 콘솔에서만 에러를 확인하던 방식과 달리, Sentry를 쓰면 프로덕션 환경에서 실제 사용자가 마주친 에러를 스택 트레이스와 함께 받아볼 수 있다.
특히 프론트엔드 입장에서 유용한 기능을 몇 가지 꼽자면:
- 에러 수집 및 알림: 예상치 못한 런타임 에러를 자동으로 캡처해서 슬랙이나 이메일로 알려준다.
- 분산 추적(Distributed Tracing): 프론트엔드 요청이 백엔드 어느 구간에서 느려졌는지 연결해서 볼 수 있다.
- Session Replay: 에러가 발생했을 때 사용자의 화면을 재현해서 재현 시나리오를 파악하는 데 도움을 준다.
쿼터가 있는 유료 서비스이기 때문에 설정을 어떻게 하느냐에 따라 비용 차이가 꽤 날 수 있다. 그래서 환경 분리와 샘플링 설정이 생각보다 중요하다.
기본 설정: @sentry/react + Vite 플러그인
패키지 설치
pnpm add @sentry/react
pnpm add -D @sentry/vite-plugin
@sentry/react는 런타임에 에러를 수집하는 SDK이고, @sentry/vite-plugin은 빌드 시 소스맵을 Sentry 서버에 자동으로 업로드해주는 플러그인이다. 소스맵을 업로드해두면 난독화된 프로덕션 빌드에서도 에러 위치를 원본 코드 기준으로 확인할 수 있다.
Sentry 초기화 (main.tsx)
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 1.0,
tracePropagationTargets: [
'localhost',
/^https:\/\/api\.your-domain\.com/,
],
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
각 옵션이 무엇을 하는지 하나씩 살펴보자.
DSN (Data Source Name)
Sentry 프로젝트의 식별자이자 엔드포인트 URL이다. Sentry 대시보드에서 프로젝트를 생성하면 발급받을 수 있다. 이 값은 외부에 노출되어도 수집만 될 뿐 악용 가능성은 낮지만, 관례적으로 환경 변수(VITE_SENTRY_DSN)로 관리하는 것이 좋다.
tracePropagationTargets
분산 추적의 핵심 설정이다. Sentry는 프론트엔드에서 백엔드로 나가는 HTTP 요청에 추적 헤더(sentry-trace, baggage)를 자동으로 삽입하는데, 이 헤더를 어떤 URL에 붙일지를 이 옵션으로 제어한다.
백엔드 API 도메인을 여기에 넣지 않으면, 프론트엔드 트랜잭션과 백엔드 트랜잭션이 연결되지 않아서 분산 추적이 동작하지 않는다. 반대로 너무 광범위하게 설정하면 서드파티 서비스에도 추적 헤더가 붙어 예상치 못한 동작이 생길 수 있다.
replaysSessionSampleRate와 replaysOnErrorSampleRate
Session Replay는 사용자의 화면을 녹화하는 기능인 만큼 비용 소모가 크다. replaysSessionSampleRate는 전체 세션 중 몇 %를 녹화할지, replaysOnErrorSampleRate는 에러가 발생한 세션을 몇 %나 녹화할지를 설정한다.
비용 최적화를 위해서는 replaysSessionSampleRate를 낮게(예: 0.1, 즉 10%) 유지하고, 에러 발생 시에는 전량 수집(1.0)하는 것이 일반적인 전략이다.
React 18에서의 ErrorBoundary 패턴
React 18에서 Sentry를 통합할 때는 Sentry.ErrorBoundary 컴포넌트를 사용하는 것이 권장 방식이다.
import * as Sentry from '@sentry/react';
function App() {
return (
<Sentry.ErrorBoundary fallback={<p>에러가 발생했습니다.</p>}>
<Router />
</Sentry.ErrorBoundary>
);
}
Sentry.ErrorBoundary는 React의 클래스 기반 ErrorBoundary를 내부적으로 구현하면서, 에러 발생 시 자동으로 Sentry에 리포트까지 해주는 래퍼 컴포넌트다. 덕분에 별도의 componentDidCatch 구현 없이도 에러 수집이 된다.
💡 버전 참고: React 19부터는
createRoot옵션으로 에러를 처리하는 방식으로 변경될 예정이다. 현재 React 18을 사용하고 있다면 위의ErrorBoundary방식을 사용하면 된다.
Vite 플러그인 설정 (vite.config.ts)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { sentryVitePlugin } from '@sentry/vite-plugin';
export default defineConfig({
build: {
sourcemap: true, // 소스맵 생성 활성화
},
plugins: [
react(),
sentryVitePlugin({
org: 'your-org-slug',
project: 'your-project-slug',
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
],
});
sentryVitePlugin은 빌드(pnpm build) 시점에 생성된 소스맵을 Sentry에 자동으로 업로드하고, 업로드된 소스맵 파일은 번들에서 제거해준다. SENTRY_AUTH_TOKEN은 Sentry 대시보드의 Settings → Auth Tokens에서 발급받을 수 있으며, 절대 코드에 하드코딩하지 않고 CI/CD 환경 변수나 .env.local로 관리해야 한다.
환경 분리: 개발 환경에서는 Sentry를 끄자
개발 중에 발생하는 수많은 에러와 이벤트까지 Sentry로 전송하면 쿼터를 빠르게 소모할 수 있다. import.meta.env.PROD를 활용해 프로덕션 빌드에서만 Sentry를 활성화하는 것이 깔끔하다.
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
enabled: import.meta.env.PROD, // 프로덕션 빌드에서만 활성화
// ... 나머지 옵션
});
import.meta.env.PROD는 Vite가 제공하는 내장 환경 변수로, pnpm build로 생성된 빌드에서는 true, pnpm dev로 실행하는 개발 서버에서는 false가 된다.
로컬에서 프로덕션 빌드 테스트하기
본 서버에 배포하기 전에 Sentry 연동이 제대로 동작하는지 로컬에서 먼저 확인하고 싶을 때는 아래 명령어가 유용하다.
pnpm build && pnpm preview
pnpm preview는 빌드 결과물을 로컬에서 서빙해주는 명령어인데, 이때 import.meta.env.PROD가 true로 동작하기 때문에 Sentry가 실제로 이벤트를 전송한다. 배포 전 Sentry 설정을 검증하는 가장 빠른 방법이다.
개발 중에 Sentry SDK 동작을 콘솔로 확인하고 싶다면 debug: true 옵션을 추가하면 된다. 이 옵션을 켜두면 이벤트가 실제로 전송되지 않더라도 콘솔에서 SDK의 동작 로그를 볼 수 있다.
pnpm 사용 시 주의사항
pnpm을 쓰는 프로젝트라면 Sentry 설정 과정에서 pnpm 특유의 동작 방식 때문에 한 번쯤 마주칠 수 있는 포인트가 있다.
1. pnpm의 심링크 격리 구조 이해하기
npm이나 yarn과 달리, pnpm은 패키지를 node_modules/.pnpm/ 디렉토리 안에 격리해서 저장한다. 최상위 node_modules에는 실제 파일이 아닌 심링크만 올라온다.
이 구조 때문에 node_modules/.bin/sentry-cli 같은 경로에서 바이너리를 직접 찾으려 하면 없는 것처럼 보일 수 있다. 하지만 실제 빌드에서는 문제가 없다. @sentry/vite-plugin이 @sentry/cli를 직접 의존성으로 참조하기 때문에, 빌드 시점에 pnpm이 올바른 경로를 통해 바이너리를 실행한다.
바이너리가 제대로 설치되었는지 확인하고 싶다면 아래처럼 pnpm exec를 사용하면 된다.
pnpm exec sentry-cli --version
2. ignoredBuiltDependencies와 onlyBuiltDependencies 충돌 주의
pnpm을 처음 설정하거나 pnpm install 과정에서 approve-builds 대화형 프롬프트를 아무것도 선택하지 않고 넘기면, pnpm이 자동으로 pnpm-workspace.yaml에 ignoredBuiltDependencies 항목을 추가하는 경우가 있다.
# pnpm-workspace.yaml (자동 생성 예시)
ignoredBuiltDependencies:
- '@sentry/cli'
ignoredBuiltDependencies는 "이 패키지의 빌드 스크립트를 실행하지 마라"는 의미다. @sentry/cli는 설치 시 플랫폼에 맞는 바이너리를 내려받는 빌드 스크립트가 필요한데, 이 항목에 들어가버리면 바이너리가 설치되지 않아 Sentry 소스맵 업로드가 실패할 수 있다.
반대로 package.json의 pnpm.onlyBuiltDependencies에 @sentry/cli를 명시해두었더라도, ignoredBuiltDependencies가 이를 덮어쓰는 방향으로 충돌이 발생할 수 있으니 주의가 필요하다.
해결 방법: 단일 프로젝트(모노레포가 아닌 경우)라면 pnpm-workspace.yaml 파일 자체가 필요 없으므로 삭제하면 깔끔하게 해결된다. 만약 모노레포 구조라면 ignoredBuiltDependencies 목록에서 @sentry/cli를 제거하고, onlyBuiltDependencies로 명시적으로 허용하는 것이 좋다.
// package.json
{
"pnpm": {
"onlyBuiltDependencies": ["@sentry/cli"]
}
}
마치며
Sentry는 설치 자체보다 설정을 어떻게 하느냐가 더 중요한 도구라는 걸 정리하면서 다시 한번 느꼈다. 특히 tracePropagationTargets 하나로 백엔드 추적 연결 여부가 갈리고, 샘플링 설정 하나로 비용이 크게 달라지기 때문에 각 옵션의 의미를 이해하고 넣는 것이 중요하다.
pnpm을 쓰는 환경이라면 pnpm-workspace.yaml의 ignoredBuiltDependencies가 조용히 문제를 일으킬 수 있다는 점도 기억해두면 좋겠다. 빌드는 통과하는데 소스맵 업로드가 안 된다면 이 부분을 먼저 확인해보자.