Files
sam-react-prod/claudedocs/vercel/vercel-deployment-setup.md
유병철 55e0791e16 refactor(WEB): Server Action 공통화 및 보안 강화
- executeServerAction 공통 유틸 도입으로 actions.ts 대폭 간소화 (50+개 파일)
- sanitize 유틸 추가 (XSS 방지)
- middleware CSP 헤더 추가 및 Open Redirect 방지
- 프록시 라우트 로깅 개발환경 한정으로 변경
- 프로덕션 불필요 console.log 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 16:14:06 +09:00

19 KiB
Raw Permalink Blame History

Vercel 배포 프론트엔드 설정 내역

작성일: 2026-02-09


1. Puppeteer 패키지 교체

변경 내용

항목 Before After
패키지 puppeteer puppeteer-core + @sparticuz/chromium

왜 교체해야 하는가

puppeteer는 설치 시 **Chromium 브라우저 전체(~170MB)**를 함께 다운로드한다. 이는 로컬/Docker 환경에서는 문제없지만, Vercel 서버리스 함수는 **패키지 크기 제한(50MB 압축, 250MB 비압축)**이 있어 배포 자체가 불가능하다.

로컬 vs Vercel 차이

환경 Chromium 제공 방식 설정
로컬 (macOS) 사용자 PC에 설치된 Google Chrome 사용 PUPPETEER_EXECUTABLE_PATH 환경변수로 경로 지정
Vercel (서버리스) @sparticuz/chromium이 AWS Lambda용 경량 Chromium 제공 chromium.executablePath()로 자동 경로 획득

분기 처리 코드 (src/app/api/pdf/generate/route.ts)

const isVercel = process.env.VERCEL === '1'; // Vercel이 자동 주입하는 환경변수

const browser = await puppeteer.launch({
  args: isVercel ? chromium.args : ['--no-sandbox', ...],
  executablePath: isVercel
    ? await chromium.executablePath()     // Vercel: @sparticuz/chromium 경량 바이너리
    : process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/google-chrome-stable',  // 로컬: 시스템 Chrome
  headless: true,
});
  • process.env.VERCEL === '1': Vercel 배포 환경에서 자동으로 설정되는 값
  • chromium.args: 서버리스 환경에 최적화된 Chromium 실행 인자 (싱글 프로세스, SwiftShader 등)

로컬 환경변수 설정 (.env.local)

PUPPETEER_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome

기존 puppeteer는 Chromium을 자체 번들했으므로 경로 지정이 불필요했지만, puppeteer-core는 브라우저를 포함하지 않으므로 로컬에서도 Chrome 경로를 명시해야 한다.


2. next.config.ts 변경

serverExternalPackages

Before After
['puppeteer'] ['puppeteer-core', '@sparticuz/chromium']

Webpack이 이 패키지들을 번들에 포함하지 않고 Node.js 런타임에서 직접 로드하도록 지정. @sparticuz/chromium은 바이너리 파일을 포함하고 있어 번들링하면 깨진다.

TypeScript / ESLint 빌드 검사

항목 Before After 이유
typescript.ignoreBuildErrors true false Vercel 배포 시 타입 에러가 런타임 버그로 이어질 수 있으므로 빌드 단계에서 차단
eslint.ignoreDuringBuilds true true (유지) 기존 미사용 import 에러 791개 존재, 점진적 해결 예정

3. vercel.json 생성

{
  "regions": ["icn1"],
  "functions": {
    "src/app/api/pdf/generate/route.ts": {
      "memory": 1024,
      "maxDuration": 30
    }
  }
}
설정 이유
regions icn1 (서울) 사용자가 한국에 위치, 백엔드 API도 한국 서버
memory 1024MB Chromium 브라우저 실행에 최소 512MB 이상 필요, PDF 렌더링 안정성 확보
maxDuration 30초 복잡한 문서의 PDF 변환 시 기본 10초로는 부족

4. 환경변수 가이드

Vercel Dashboard에 등록해야 할 환경변수 목록은 claudedocs/vercel/vercel-env-setup-guide.md 참조.

핵심 포인트:

  • API_KEY: Sensitive 체크 필수, NEXT_PUBLIC_ 접두사 절대 금지
  • PUPPETEER_EXECUTABLE_PATH: Vercel에서는 설정 불필요 (@sparticuz/chromium이 처리)
  • VERCEL=1: Vercel이 자동 주입, 별도 설정 불필요

5. TypeScript 에러 수정

ignoreBuildErrors: false로 변경하면서 기존에 숨겨져 있던 TS 에러 16개+ 수정.

에러 유형 파일 수 원인
WorkOrderItem 프로퍼티 누락 5개 파일 orderNodeId, orderNodeName 필드 추가 후 목업 데이터 미갱신
WorkOrder 프로퍼티 누락 3개 파일 shutterCount 필드 추가 후 합성 객체 미갱신
unknownReactNode 1개 파일 Record<string, unknown> 값을 JSX에 직접 사용
object 프로퍼티 접근 2개 파일 Object.entries() 후 타입 narrowing 부족
타입 미export 1개 파일 BomCalculationResult import만 하고 re-export 안 함
implicit any 1개 파일 콜백 파라미터 타입 어노테이션 누락

변경 파일 전체 목록

수정:
  src/app/api/pdf/generate/route.ts          # Puppeteer 교체
  next.config.ts                              # 빌드 설정
  package.json / package-lock.json            # 패키지 교체
  .env.local                                  # Chrome 경로 추가
  .env.example                                # Chrome 경로 가이드 추가

신규:
  vercel.json                                 # Vercel 배포 설정
  claudedocs/vercel/vercel-env-setup-guide.md # 환경변수 가이드
  claudedocs/vercel/vercel-deployment-setup.md # 이 문서

TS 에러 수정 (13개 파일):
  src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx
  src/app/[locale]/(protected)/quality/qms/mockData.ts
  src/components/material/ReceivingManagement/actions.ts
  src/components/orders/OrderSalesDetailView.tsx
  src/components/production/WorkerScreen/index.tsx
  src/components/production/WorkerScreen/WorkLogModal.tsx
  src/components/production/WorkOrders/documents/InspectionReportModal.tsx
  src/components/production/WorkOrders/WorkOrderDetail.tsx
  src/components/production/WorkOrders/WorkOrderEdit.tsx
  src/components/quotes/LocationDetailPanel.tsx
  src/components/quotes/QuoteSummaryPanel.tsx
  src/components/quotes/QuotePreviewContent.tsx
  src/components/quotes/actions.ts

6. Vercel 비용 주의사항

비용 발생 구조

Vercel은 서버리스 함수 호출 횟수 + 실행 시간 + 대역폭으로 과금된다. 우리 프로젝트에서 비용이 튈 수 있는 요소를 분석한다.

6-1. API Proxy 패턴 (가장 큰 비용 요소)

현재 구조: 클라이언트 → /api/proxy/* (Vercel 서버리스) → PHP 백엔드

문제 설명
이중 대역폭 모든 API 응답이 Vercel 서버리스를 거쳐 클라이언트로 전달 (대역폭 2배)
함수 호출 폭발 모든 API 요청이 서버리스 함수 1회 호출 = 페이지 로드마다 수~수십 회
실행 시간 누적 각 함수가 백엔드 응답을 기다리는 시간만큼 과금

왜 이 패턴을 쓰는가: HttpOnly 쿠키에 저장된 access_token은 JavaScript로 읽을 수 없어서, 서버(서버리스 함수)에서 쿠키를 읽어 Authorization 헤더로 변환해야 한다.

예상 영향: 사용자 수가 늘어날수록 비용이 선형 증가. ERP 특성상 한 페이지에서 3-10개 API를 호출하므로, 동시 접속자 50명이면 분당 수백~수천 회 함수 호출 가능.

6-1-1. 30초 폴링 — 가장 심각한 비용 요소

프로젝트에 30초 간격 폴링이 2개 존재하며, 이것들이 서버리스 프록시를 통과한다.

폴링 파일 간격 호출 API
메뉴 폴링 src/hooks/useMenuPolling.ts 30초 refreshMenus()/api/proxy/*
알림(Today Issue) 폴링 src/layouts/AuthenticatedLayout.tsx 30초 getUnreadTodayIssues()/api/proxy/*

폴링만의 서버리스 함수 호출 추정 (동시접속 50명, 8시간 근무 기준):

메뉴 폴링:  50명 × 2회/분 × 60분 × 8시간 =  48,000회/일
알림 폴링:  50명 × 2회/분 × 60분 × 8시간 =  48,000회/일
─────────────────────────────────────────────────────────
합계:       96,000회/일 (폴링만으로, 일반 API 호출 제외)
월간:       96,000 × 22일(근무일) = 약 2,112,000회/월

Vercel Pro 플랜 기준 서버리스 함수 포함 실행량 100만 회/월이므로, 폴링만으로 2배 초과. 이 폴링 2개가 Edge Middleware로 전환 시 가장 큰 비용 절감 효과를 볼 수 있다.

참고: 두 폴링 모두 탭 비활성 시 일시정지하는 최적화가 적용되어 있지만, 활성 탭 기준으로는 위 수치가 그대로 적용된다.

6-2. PDF 생성 함수

항목 비용 영향
메모리 1024MB 기본(128MB) 대비 8배 비용
최대 실행 시간 30초 긴 실행 = 높은 과금
빈도 낮음 (수동 PDF 변환) 자동 배치가 아니므로 실제 비용 영향은 적음

PDF는 사용 빈도가 낮아 큰 문제는 아니지만, 메모리 1024MB로 설정했으므로 호출당 비용이 높다.

6-3. Server Actions (bodySizeLimit: 10MB)

next.config.ts에서 serverActions.bodySizeLimit: '10mb'로 설정. 이미지 업로드 시 큰 페이로드가 서버리스 함수를 통과하므로 대역폭 비용 발생. 단, 이미지 저장 자체는 PHP 백엔드가 처리하므로 Vercel 스토리지 비용은 없음.

6-4. 비용 안전 요소

항목 상태 이유
이미지/파일 저장 안전 PHP 백엔드 서버에 저장, Vercel에 저장 안 함
Vercel Image Optimization 미사용 remotePatternsplacehold.co만 등록 (개발용)
ISR/SSG 미사용 전체 Client Component, 정적 생성 없음

7. API Proxy 비용 절감 방안

현재 문제

클라이언트 → [Vercel 서버리스 함수] → PHP 백엔드
              ↑ 매 API 요청마다 호출
              ↑ Node.js Runtime (비쌈)

/api/proxy/[...path]/route.ts모든 백엔드 API 호출을 중계하므로 서버리스 함수 호출 횟수가 폭발적으로 증가한다.

방안 1: Edge Middleware Rewrite (프론트엔드 변경만으로 가능)

클라이언트 → [Edge Middleware] → PHP 백엔드 (직접)
              ↑ Edge Runtime (매우 저렴)
              ↑ 쿠키 읽기 가능

원리: 현재 middleware.ts는 이미 Edge Runtime에서 쿠키를 읽고 있다. 이를 확장하여 /api/proxy/* 요청을 Edge에서 백엔드로 직접 rewrite하면 서버리스 함수 호출을 제거할 수 있다.

장점:

  • Edge 함수는 서버리스 대비 10-100배 저렴 (실행 시간이 아닌 요청 수 기준)
  • 프론트엔드만 변경하면 됨 (백엔드 수정 불필요)
  • 응답 지연(latency)도 감소 (중간 서버리스 단계 제거)

한계:

  • Edge Runtime은 Node.js API 일부를 사용 못함
  • 토큰 자동 갱신(refresh) 처리가 복잡해짐 — 현재 authenticatedFetch가 401 감지 → refresh → retry를 처리하는데, Edge Middleware의 rewrite에서는 이 패턴 구현이 어려움
  • FormData/바이너리 응답 처리에 제약 가능

적용 가능 범위: 단순 GET/POST API 호출 (토큰 갱신 없이 정상 동작하는 요청)

방안 2: 백엔드 CORS 허용 + 직접 호출 (백엔드 변경 필요)

클라이언트 → PHP 백엔드 (직접)
              ↑ Vercel을 거치지 않음
              ↑ 서버리스 비용 0

원리: PHP 백엔드가 Vercel 도메인에서의 CORS를 허용하고, 쿠키를 직접 설정(SameSite=None; Secure)하면 프록시 자체가 불필요해진다.

장점:

  • 서버리스 함수 호출 완전 제거
  • 가장 큰 비용 절감 효과
  • 응답 속도 최적 (중간 단계 없음)

한계:

  • 백엔드 수정 필수: CORS 헤더, 쿠키 SameSite 정책 변경
  • HttpOnly 쿠키 도메인 문제: 프론트(Vercel)와 백엔드(PHP)가 다른 도메인이면 3rd-party 쿠키로 분류되어 브라우저 차단 가능
  • 같은 도메인/서브도메인 구조가 아니면 추가 설정 필요 (app.example.comapi.example.com)

방안 3: 하이브리드 (권장)

요청 유형 처리 방식 비용
단순 데이터 조회 (GET) Edge Middleware rewrite 매우 저렴
데이터 변경 (POST/PUT/DELETE) 기존 서버리스 프록시 유지 현행 유지
파일 업로드 (multipart) 기존 서버리스 프록시 유지 현행 유지
토큰 갱신 필요 시 기존 서버리스 프록시 유지 현행 유지

GET 요청이 전체 API 호출의 60-80%를 차지하므로, 이것만 Edge로 옮겨도 비용을 절반 이상 줄일 수 있다.

방안 비교 요약

방안 비용 절감 프론트 변경 백엔드 변경 복잡도 권장
1. Edge Rewrite 중 (60-80%) O X 단기
2. CORS 직접 호출 최대 (95%+) O O 장기
3. 하이브리드 중상 (60-80%) O X 현실적 1순위

다음 단계

  1. Vercel 배포 후 실제 함수 호출 횟수/비용 모니터링
  2. 비용이 예상보다 높으면 방안 3(하이브리드) 우선 적용
  3. 장기적으로 백엔드팀과 협의하여 방안 2(CORS) 검토

8. 월간 비용 산정 (Vercel Pro, 서울 icn1 리전)

산정일: 2026-02-09 / 출처: Vercel Pricing, Fluid Compute Pricing

8-1. Vercel 요금 구조 (Pro 플랜)

항목 Pro 포함량 초과 단가
기본 요금 $20/개발자 시트/월
사용 크레딧 $20/월 포함 (사용량 차감)
Invocations (함수 호출) 크레딧 차감 $0.60 / 100만 회
Active CPU (icn1) 크레딧 차감 $0.169 / 시간
Provisioned Memory (icn1) 크레딧 차감 $0.014 / GB-시간
Edge Requests 1,000만 회/월 ~$2 / 100만 회
Bandwidth 1TB/월 초과 시 GB당 과금

Fluid Compute 모델: Active CPU는 코드 실행 중에만 과금 (I/O 대기 시 과금 안 됨). Provisioned Memory는 인스턴스 활성 시간 전체 과금.

8-2. 트래픽 추정 전제 (실제 사업 구조 반영)

전제 비고
개발자 시트 2명 프론트엔드 2명
서비스 회사 수 50개 이하 (초기) 점진적 확장
회사당 이용자 ~5명 ERP 실사용자
총 등록 사용자 ~250명 50사 × 5명
동시접속률 30~40% ERP 특성 (전원 동시 사용 안 함)
동시접속 사용자 75100명 250명 × 30~40%
근무 시간 8시간/일
월 근무일 22일
메뉴 폴링 간격 30초 useMenuPolling.ts
알림 폴링 간격 30초 AuthenticatedLayout.tsx
페이지 이동 시 API 호출 평균 5개/페이지
페이지 이동 횟수 평균 50회/사용자/일
PDF 생성 100건/월

8-3. 월간 함수 호출 횟수 (동시접속 75명 기준)

① 메뉴 폴링:     75명 × 2회/분 × 480분 × 22일 =  1,584,000회
② 알림 폴링:     75명 × 2회/분 × 480분 × 22일 =  1,584,000회
③ 페이지 API:    75명 × 250회/일 × 22일         =    412,500회
④ PDF 생성:                                     =        100회
──────────────────────────────────────────────────────────────
합계:                                              3,580,600회/월

8-4. 시나리오 A — 현재 구조 (전부 서버리스 프록시)

모든 API 호출이 /api/proxy/[...path] 서버리스 함수를 경유.

API 프록시 1회당 비용 추정 (icn1):

  • Active CPU: ~30ms (쿠키 읽기, 헤더 구성, 응답 파싱)
  • Instance alive: ~300ms (백엔드 응답 대기 포함)
  • Memory: 128MB (0.128 GB)
항목 계산 월 비용
Invocations 3,580,000 × $0.60/1M $2.15
Active CPU 3,580,000 × 0.03s = 107,400s = 29.8hr × $0.169 $5.04
Provisioned Memory 3,580,000 × 0.128GB × 0.3s / 3600 = 38.2 GB-hr × $0.014 $0.53
PDF CPU 100 × 5s / 3600 × $0.169 $0.02
PDF Memory 100 × 1GB × 15s / 3600 × $0.014 $0.01
함수 사용량 소계 $7.75
기본 요금 개발자 2명 × $20 $40.00
사용 크레딧 -$20 (포함) -$20.00
예상 월 합계 ~$28

함수 사용량 $7.75는 $20 크레딧 이내. 초과 과금 없음.

8-5. 시나리오 B — 하이브리드 (폴링 + GET을 Edge로)

폴링 2개 + 일반 GET을 Edge Middleware rewrite로 전환. POST/PUT/DELETE/파일업로드만 서버리스 유지.

Edge로 이동:    폴링 3,168,000 + GET ~300,000 = ~3,468,000 → Edge Requests (10M 포함 이내)
서버리스 유지:  POST/PUT/DELETE ~112,500 + PDF 100 = ~112,600
항목 계산 월 비용
Edge Requests 3,468,000회 (10M 포함 이내) $0
Invocations 112,600회 × $0.60/1M $0.07
Active CPU 112,600 × 0.05s / 3600 × $0.169 $0.26
Provisioned Memory 112,600 × 0.128GB × 0.4s / 3600 × $0.014 $0.02
PDF (위와 동일) $0.03
함수 사용량 소계 $0.38
기본 요금 개발자 2명 × $20 $40.00
사용 크레딧 -$20 (포함) -$20.00
예상 월 합계 ~$20

8-6. 성장 단계별 비용 비교

단계 서비스 회사 총 사용자 동시접속 시나리오 A 시나리오 B
초기 10사 50명 ~20명 ~$22 (함수 $2) ~$20 (함수 $0.1)
안정기 50사 250명 ~75명 ~$28 (함수 $8) ~$20 (함수 $0.4)
성장기 100사 500명 ~150명 ~$35 (함수 $15) ~$21 (함수 $0.8)
확장기 200사 1,000명 ~300명 ~$50 (함수 $30) ~$22 (함수 $1.5)

모든 단계에서 고정 비용 = 개발자 2시트 $40 크레딧 $20 = $20. 함수 비용은 이 $20 크레딧에서 차감되며, 초기~안정기에는 크레딧 이내로 해결됨.

8-7. 결론

  • 초기~안정기 (50사 이하): 월 $2228. 함수 비용 $8 이하로 크레딧 $20 이내. 최적화 불필요.
  • 성장기 (100사): 함수 비용 $15. 아직 크레딧 이내이지만 여유 줄어듦. 하이브리드 전환 검토 시점.
  • 확장기 (200사 이상): 함수 비용이 크레딧 초과 → 추가 과금 발생. 하이브리드 전환 필수.
  • 핵심 비용은 개발자 시트. 프론트 2명 기준 고정 $20/월. 함수 비용은 초기에 미미.
  • 폴링 2개만 Edge로 전환해도 함수 비용 95% 절감 가능 (100사 기준 $15 → $0.8).

배포 전 체크리스트

  • puppeteer-core + @sparticuz/chromium 설치
  • PDF 라우트 로컬/Vercel 분기 처리
  • next.config.ts 설정 변경
  • vercel.json 생성
  • TypeScript 에러 0개 확인
  • 로컬 Chrome 경로 설정 (.env.local)
  • 로컬 PDF 변환 테스트
  • Vercel Dashboard 환경변수 등록
  • Vercel 배포 및 PDF 변환 테스트