Vercel로 첫 Next.js 앱 배포하기 — 비개발자가 겪은 빌드 에러들
localhost에서는 완벽했는데 Vercel에서 200개 에러
로컬에서 7개월간 개발한 로또 분석 앱을 처음 Vercel에 배포했습니다. 빌드 버튼을 눌렀더니... 빨간 에러가 200개 넘게 쏟아졌습니다. 로컬에서는 경고(warning)로 조용히 넘어가던 것들이, Vercel 빌드에서는 에러(error)로 전부 터진 거예요.
Before — 초급 프롬프트:Vercel 배포 안 돼. 에러 고쳐줘.
After — 중급 프롬프트:
당신은 Next.js 15 빌드 에러 전문가입니다.
[현재 상태]
- Next.js 15.5.2 + TypeScript strict 모드
- 로컬 dev 서버에서는 정상 작동
- Vercel 프로덕션 빌드에서 200개 이상 타입 에러 발생
[에러 카테고리]
1. Dynamic route params에 Promise 타입 필요 (Next.js 15 변경)
2. headers(), cookies() 호출에 await 누락
3. 100개 이상 implicit any 타입
4. 컴포넌트 props 타입 불일치
[요청]
카테고리별로 일괄 수정하는 패턴을 알려주세요.
한 파일씩 고치면 끝이 없으니, 패턴별 일괄 적용 방법이 필요합니다.
핵심은 "200개 에러를 한 파일씩 물어보지 않고, 패턴별로 묶어서 해결"하는 프롬프트였습니다.
빌드 에러 1: Next.js 15가 바꿔놓은 것들
Next.js 15에서 가장 큰 변경은 dynamic route params가 Promise 타입으로 바뀐 것이었습니다. 기존 코드가 전부 깨졌거든요.
[에러 메시지]
Type '{ params: { id: string } }' is not assignable to type
'{ params: Promise<{ id: string }> }'
당신은 Next.js 15 마이그레이션 전문가입니다.
[에러]
모든 dynamic route 페이지에서 params 타입 에러 발생.
Next.js 15에서 params가 Promise로 변경된 것 같습니다.
[요청]
1. 변경된 타입 패턴을 알려주세요
2. 모든 [id], [round], [slug] 페이지에 일괄 적용하는 방법
3. headers(), cookies()도 await가 필요해진 건지 확인
AI가 알려준 패턴:
// Before (Next.js 14)
export default function Page({ params }: { params: { id: string } }) {
const id = params.id;
}
// After (Next.js 15)
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
}
이 패턴 하나로 dynamic route 에러가 일괄 해결됐습니다. headers()와 cookies()도 마찬가지로 await가 필요했고요.
빌드 에러 2: "이 페이지는 정적으로 만들 수 없습니다"
Error: Dynamic server usage: Page couldn't be rendered statically
because it uses cookies()
이 에러는 Next.js가 빌드할 때 "이 페이지를 미리 HTML로 만들어둘까?" 판단하는 과정에서 터집니다. cookies()처럼 요청마다 달라지는 걸 쓰면 미리 만들 수가 없거든요.
[피드백]
프리미엄 예측 페이지에서 "Dynamic server usage" 에러가 나옵니다.
이 페이지는 로그인 상태를 확인해야 해서 cookies()를 사용 중입니다.
[요청]
페이지를 동적 렌더링으로 전환하는 방법을 알려주세요.
해결은 한 줄이었습니다:
// 페이지 파일 상단에 추가
export const dynamic = 'force-dynamic';
"이 페이지는 매번 새로 만들어"라고 Next.js한테 알려주는 거예요. 로그인 상태를 확인하는 페이지, API route 중 cookies()를 쓰는 곳에 전부 이걸 추가했습니다.
빌드 에러 3: 파일이 깨져있었다
가장 황당한 에러는 이거였습니다. 특정 페이지 파일이 UTF-8이 아닌 바이너리 포맷으로 저장되어 있어서 빌드 단계에서 파싱 자체가 안 되는 거예요.
[에러 상황]
auth/password-updated/page.tsx와 auth/update-password/page.tsx
2개 파일에서 빌드 시 파싱 오류 발생. 코드는 문제없어 보임.
[요청]
파일 인코딩 문제인지 확인하고, UTF-8로 재생성하는 방법을 알려주세요.
AI가 파일을 확인해보니 인코딩이 깨져 있었습니다. 파일 내용은 같은 코드로 새 파일을 만들어서 교체하니 해결. 이건 에러 메시지만 보고는 절대 원인을 못 찾는 종류였어요.
환경변수 — "링크 생성에 실패했습니다"
빌드는 통과했는데, 프로덕션에서 비밀번호 재설정을 하니까 "링크 생성에 실패했습니다" 에러가 떴습니다.
당신은 Vercel 환경변수 전문가입니다.
[에러]
프로덕션에서 비밀번호 재설정 이메일 발송 시
"링크 생성에 실패했습니다" 오류
[현재 상태]
- 로컬에서는 정상 작동
- Vercel에 배포된 프로덕션에서만 에러
[요청]
Vercel 환경변수 설정을 확인해주세요.
로컬 .env.local에 있는 변수 중 Vercel에 미설정된 것이 있는지.
원인: RESEND_API_KEY가 Vercel에 설정되어 있지 않았습니다. 로컬 .env.local에만 있던 거예요.
이런 식으로 하나씩 터지면서 알게 된 건, 환경변수를 Vercel에 등록한 후 반드시 재배포(Redeploy)해야 한다는 것이었습니다. 환경변수만 추가하면 자동으로 반영되는 게 아니거든요.
Vercel에 등록해야 할 필수 환경변수:
| 변수 | 용도 | 주의점 |
|------|------|--------|
| NEXT_PUBLIC_SUPABASE_URL | Supabase 연결 | NEXT_PUBLIC_ = 브라우저에 노출됨 |
| SUPABASE_SERVICE_ROLE_KEY | 서버 전용 키 | NEXT_PUBLIC_ 없음 = 서버만 접근 |
| RESEND_API_KEY | 이메일 발송 | 미설정 시 비밀번호 재설정 실패 |
| NEXTAUTH_URL | 인증 리다이렉트 | 프로덕션 URL로 변경 필수 |
| NEXTAUTH_SECRET | 세션 암호화 | 로컬과 다른 값 사용 권장 |
preferredRegion — auto에서 icn1로
배포 후 API 응답이 느리다는 느낌이 있었습니다. Supabase 데이터베이스는 한국 리전인데, Vercel 서버리스 함수는 미국에서 실행되고 있었거든요. 한국 → 미국 → 한국으로 데이터가 왕복하는 구조였습니다.
[문제]
API 응답이 느림. Supabase는 한국인데 Vercel 함수는 미국에서 실행.
preferredRegion을 'auto'로 설정했는데 개선 안 됨.
[요청]
한국 사용자 대상일 때 최적 리전 설정 방법을 알려주세요.
'auto'가 기대처럼 동작하지 않아서 'icn1'(인천)로 직접 지정했습니다. API route 파일에 한 줄 추가:
export const preferredRegion = 'icn1';
이후 Vercel이 'auto' 옵션 지원을 변경하면서, 결국 'icn1'을 직접 지정하는 게 정답이 됐습니다.
도메인 연결 — DNS 전파를 기다리는 인내심
커스텀 도메인(pick6.kr) 연결은 Vercel에서 도메인 추가 → 도메인 업체 DNS에 CNAME 레코드 추가로 끝나는 간단한 작업이었습니다.
문제는 DNS 전파 시간이었어요. 설정을 마치고 30분이 지나도 연결이 안 되니까 "설정이 틀렸나?" 하면서 여러 번 수정했거든요. AI한테 물었더니 "설정 건드리지 말고 기다리세요. DNS 전파에 최대 48시간 걸릴 수 있습니다"라고 했습니다. 실제로 2시간 후에 연결됐고요.
200개 에러 → 0개 에러, 최종 결과
200개 넘는 TypeScript 에러를 패턴별로 묶어서 해결하고, 빌드 시간도 최적화했습니다:
| 항목 | Before | After |
|------|--------|-------|
| TypeScript 에러 | 200개+ | 0개 |
| 프로덕션 빌드 | 39.8초 | 15.9초 (60% 감소) |
| 초기 번들 크기 | 2.1MB | 1.5MB (28.6% 감소) |
| API 평균 응답 | 885ms | 71ms (icn1 리전) |
Next.js 15 호환성, 환경변수, 리전 설정, 인코딩 문제... 전부 "로컬에서는 되는데" 카테고리였습니다. AI한테 시킬 때 "배포 안 돼"가 아니라 빌드 로그의 에러 메시지를 그대로 복사해서 주는 것, 그리고 에러를 패턴별로 묶어서 한번에 해결을 요청하는 것이 핵심이었습니다.