# 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`)

```typescript
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 생성

```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` 필드 추가 후 합성 객체 미갱신 |
| `unknown` → `ReactNode` | 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 | 미사용 | `remotePatterns`에 `placehold.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.com` ↔ `api.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](https://vercel.com/pricing), [Fluid Compute Pricing](https://vercel.com/docs/functions/usage-and-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 특성 (전원 동시 사용 안 함) |
| **동시접속 사용자** | **~75~100명** | 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사 이하)**: 월 **~$22~28**. 함수 비용 $8 이하로 **크레딧 $20 이내**. 최적화 불필요.
- **성장기 (100사)**: 함수 비용 $15. 아직 크레딧 이내이지만 여유 줄어듦. 하이브리드 전환 검토 시점.
- **확장기 (200사 이상)**: 함수 비용이 크레딧 초과 → 추가 과금 발생. 하이브리드 전환 필수.
- **핵심 비용은 개발자 시트**. 프론트 2명 기준 고정 $20/월. 함수 비용은 초기에 미미.
- 폴링 2개만 Edge로 전환해도 함수 비용 **95% 절감** 가능 (100사 기준 $15 → $0.8).

---

## 배포 전 체크리스트

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