Files
zellyy-finance/vite.config.ts
hansoo 3934ab933f fix: Clerk 패키지 설치 및 Vite 빌드 설정 수정
- @clerk/clerk-react 패키지 설치 추가
- Vite external 설정에서 Clerk 번들링 허용으로 변경
- ChunkLoadError 복구 시스템 Playwright 테스트 추가
- Clerk CDN 실패 시나리오 검증 및 Mock 인증 폴백 시스템 확인

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-14 14:12:40 +09:00

250 lines
7.8 KiB
TypeScript

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
import { visualizer } from "rollup-plugin-visualizer";
import { sentryVitePlugin } from "@sentry/vite-plugin";
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
server: {
host: "0.0.0.0",
port: 3000,
cors: true,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
},
plugins: [
react(),
visualizer({
filename: "dist/stats.html",
open: false,
gzipSize: true,
brotliSize: true,
}),
// Sentry 플러그인 (프로덕션 빌드 + Auth Token 있을 때만 활성화)
mode === "production" &&
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_DISABLE_SOURCEMAPS !== "true" &&
sentryVitePlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
// 소스맵 업로드 설정
sourcemaps: {
// 빌드된 모든 JS, CSS 파일의 소스맵 업로드
assets: ["./dist/**/*.js", "./dist/**/*.css"],
// 업로드 후 로컬 소스맵 파일 삭제 (보안)
filesToDeleteAfterUpload: ["./dist/**/*.map"],
// 소스맵 업로드 재시도 설정
rewrite: true,
// 업로드 진행상황 표시
verbose: true,
},
// 릴리즈 설정
release: {
name:
process.env.SENTRY_RELEASE ||
`zellyy-finance@${new Date().toISOString().slice(0, 19)}`,
// 릴리즈에 Git 커밋 정보 자동 첨부
setCommits: {
auto: true,
ignoreMissing: true,
},
// 배포 환경 정보
deploy: {
env: mode,
name: `${mode}-deployment`,
url: process.env.VERCEL_URL || process.env.DEPLOY_URL,
},
// 릴리즈 완료 처리
finalize: true,
},
// 에러 리포팅 설정
errorHandler: (err) => {
console.warn("Sentry 소스맵 업로드 실패:", err.message);
// 빌드는 계속 진행 (소스맵 업로드 실패로 빌드가 중단되지 않도록)
return false;
},
// 텔레메트리 비활성화
telemetry: false,
// 디버그 모드 (개발자가 문제를 진단할 수 있도록)
debug: process.env.SENTRY_DEBUG === "true",
// 업로드 타임아웃 설정 (네트워크 상황에 따라 조정)
uploadTimeout: 120000, // 2분
}),
].filter(Boolean),
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
// 소스맵 생성 설정 (프로덕션에서도 에러 디버깅을 위해 생성)
sourcemap: mode === "production" ? "hidden" : true,
// 고급 Tree shaking 최적화
treeshake: {
// 모듈 사이드 이펙트 제거 (CSS imports 제외)
moduleSideEffects: (id) => {
return (
id.includes(".css") || id.includes(".scss") || id.includes(".less")
);
},
// 프로퍼티 읽기 사이드 이펙트 제거
propertyReadSideEffects: false,
// 사용하지 않는 클래스 및 함수 제거
tryCatchDeoptimization: false,
// 알려진 글로벌 사이드 이펙트 제거
unknownGlobalSideEffects: false,
},
// 청크 로딩 실패에 대한 재시도 설정
target: "esnext",
rollupOptions: {
// 외부 종속성 명시적 처리 (CDN 오류 방지)
external: (id) => {
// 빌드 시 @clerk 모듈을 정상적으로 번들에 포함시키고,
// 런타임에서만 조건부로 비활성화
return false;
},
output: {
// 청크 파일명 일관성 보장 (ChunkLoadError 방지)
chunkFileNames: "assets/[name]-[hash].js",
entryFileNames: "assets/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash].[ext]",
// 청크 로딩 실패 시 재시도 로직 추가
intro: `
window.__vitePreloadOriginal = window.__vitePreload;
window.__vitePreload = function(baseModule, deps) {
return window.__vitePreloadOriginal(baseModule, deps).catch(err => {
console.warn('Chunk loading failed, retrying...', err);
// 청크 오류 처리 시스템이 이미 활성화되어 있으므로 에러를 다시 던짐
throw err;
});
};
`,
manualChunks: (id) => {
// 노드 모듈들을 카테고리별로 분할
if (id.includes("node_modules")) {
// 핵심 React 라이브러리
if (id.includes("react") || id.includes("react-dom")) {
return "vendor-react";
}
// 라우팅
if (id.includes("react-router")) {
return "vendor-router";
}
// UI 컴포넌트 라이브러리 (Radix UI)
if (id.includes("@radix-ui")) {
return "vendor-ui";
}
// 차트 라이브러리 (Recharts - 가장 큰 청크)
if (id.includes("recharts")) {
return "vendor-charts";
}
// 상태 관리 및 쿼리
if (
id.includes("@tanstack/react-query") ||
id.includes("zustand")
) {
return "vendor-state";
}
// 인증 및 백엔드
if (id.includes("@clerk") || id.includes("@supabase")) {
return "vendor-auth";
}
// 모니터링 및 에러 추적
if (id.includes("@sentry")) {
return "vendor-sentry";
}
// 유틸리티 라이브러리
if (
id.includes("date-fns") ||
id.includes("clsx") ||
id.includes("tailwind-merge")
) {
return "vendor-utils";
}
// 폼 관련
if (
id.includes("react-hook-form") ||
id.includes("@hookform") ||
id.includes("zod")
) {
return "vendor-forms";
}
// 나머지 모든 노드 모듈들
return "vendor-misc";
}
// 소스 코드를 기능별로 분할
if (id.includes("/src/")) {
// 페이지 컴포넌트들
if (id.includes("/pages/")) {
return "pages";
}
// 차트 및 분석 관련 컴포넌트
if (id.includes("/analytics/") || id.includes("Chart")) {
return "analytics";
}
// 거래 관련 컴포넌트
if (id.includes("/transaction/") || id.includes("Transaction")) {
return "transactions";
}
// 예산 관련 컴포넌트
if (id.includes("/budget/") || id.includes("Budget")) {
return "budget";
}
// 인증 관련 컴포넌트
if (id.includes("/auth/") || id.includes("Auth")) {
return "auth";
}
// UI 컴포넌트
if (id.includes("/ui/")) {
return "ui-components";
}
// 훅 및 유틸리티
if (
id.includes("/hooks/") ||
id.includes("/utils/") ||
id.includes("/lib/")
) {
return "core-utils";
}
}
},
},
},
// 청크 크기 경고 임계값 조정
chunkSizeWarningLimit: 1000,
// 압축 최적화 (esbuild 사용)
minify: "esbuild",
},
}));