Vercel에 Next.js 올리다 보면 별거 아닌데 막히는 지점들이 꼭 있더라구요. 한 번에 정리해둘게요. 제가 실수했던 것들도 섞여 있어요. 작은 것부터 큼직한 것까지.
환경변수는 우선 분리부터요. Vercel은 Development/Preview/Production이 따로라서 로컬 .env만 믿고 올리면 빈 값으로 빌드돼요. 특히 클라이언트에서 쓰는 값은 NEXT\_PUBLIC\_ 접두사가 없으면 번들에 안 들어가고요. 프리뷰 브랜치에선 다른 키를 써야 안전합니다.
이미지 최적화 때문에 404 자주 나요. 외부 이미지를 쓰면 next.config.js에 images.domains나 remotePatterns로 호스트를 꼭 등록해야 합니다. 프로토콜까지 정확히. basePath를 쓰면 정적 파일 경로도 달라져서 public 경로가 갑자기 안 맞는 일이 있어요. 이럴 땐 이미지/링크 앞에 basePath 붙었는지 한 번 더 확인합니다.
앱 라우터로 오면 fetch가 기본 캐시임을 깜박해요. 서버에서 매 요청 새 데이터를 원하면 cache: 'no-store'나 revalidate 값을 명시해야 해요. 안 그러면 프리뷰에서만 오래된 데이터가 남기도 해요. ISR을 쓰면 revalidate 숫자만 믿지 말고 on-demand revalidation 토큰도 같이 세팅해두면 콘텐츠 갱신 타이밍을 통제하기 편하고요.
미들웨어는 매처 설정이 과하면 리다이렉트 루프가 나요. i18n, trailingSlash, rewrites와 함께 쓰면 더 잘 꼬이고요. 공통 패턴은 미들웨어에서 정적 파일(.\_next, images, favicon 등)은 제외시키고, rewrite/redirect 규칙은 minimal로 유지하는 겁니다. 규칙이 겹칠 땐 가장 포괄적인 걸 아래로 내리는 게 안전해요.
서버 런타임은 Edge/Node 구분이 은근 큽니다. Edge에선 Node 내장모듈(fs, crypto 일부, buffer 의존 라이브러리 등) 못 써요. 라우트 핸들러나 미들웨어를 Edge로 지정해놓고 Node 전용 라이브러리를 import하면 바로 터져요. 반대로 Node 서버리스 함수는 용량, 메모리, 실행시간 제한이 있어서 대형 번들(예: 대형 SDK)이나 헤비한 PDF 생성은 빌드 최적화가 필요합니다. prisma 같이 바이너리가 필요한 애들은 Edge 대신 Node로 고정하고, 빌드 후 경로 문제를 피하려면 prisma generate를 빌드 단계에서 돌리세요.
패키지 매니저는 하나로 통일. yarn.lock과 package-lock.json이 같이 있으면 Vercel이 헷갈려요. 루트가 모노레포면 프로젝트 설정에서 Root Directory 명확히 잡고, Install/Build/Output 디렉터리도 프로젝트별로 지정합니다. monorepo에서 빌드가 불필요하게 자주 도는 게 싫으면 Ignore Build Step 스크립트로 변경 없는 커밋은 스킵하는 것도 팁이고요.
ESLint 때문에 빌드가 멈추는 경우가 꽤 많아요. 프리뷰에서만 느슨하게 하고 싶다면 NEXT\_DISABLE\_ESLINT\_PLUGIN 같은 우회도 있지만, 결국은 규칙을 프로젝트에 맞게 다듬는 게 답이에요. TypeScript strict 옵션을 켰다면 빌드에 영향을 주는 ts 오류가 없는지 먼저 로컬에서 tsc --noEmit로 확인하고 올리는 습관이 편합니다.
정적/동적의 경계도 체크해야 해요. app 라우터에서 dynamic = 'force-static'으로 잠궈놨는데 서버 요청을 추가하면 빌드가 경고를 내고 의도치 않게 동적이 되기도 해요. 반대로 getServerSideProps를 pages에서 쓰면서 CDN 캐시를 기대하면 안 되고요. 혼용하는 프로젝트는 어느 라우터가 소스 오브 트루스인지부터 정리하는 게 정신 건강에 좋아요.
next/image가 sharp를 내부에서 쓰는데, 로컬과 CI의 sharp 바이너리 차이로 이미지 처리 실패가 드물게 나요. 이런 경우는 이미지 최적화를 Vercel 빌트인으로 두되, 로컬에선 optionalDependencies 설치 이슈만 해결하면 대부분 정리돼요. SVG를 next/image로 다루는 건 여전히 케이스 바이 케이스라, 그냥 img로 쓰는 게 속 편할 때가 많습니다.
경로 대소문자, 사소하지만 리눅스 파일시스템에서만 터지는 고질병이에요. import 경로의 대소문자와 실제 파일명이 한 글자라도 다르면 로컬 맥에선 되던 게 서버에서만 모듈 못 찾는 오류가 납니다. 특히 components/Header.tsx를 components/header로 임포트하는 식의 습관, 한 번쯤 grep으로 잡아내세요.
마지막으로 에셋 크기와 API 제한. 빌드 산출물이 너무 크면 서버리스 로딩이 느려지고 콜드스타트가 체감돼요. editor 전용 거대한 JSON, 폰트, 지도 SDK 같은 건 동적 import로 나누거나, public CDN으로 빼는 편이 낫습니다. API Route의 요청 본문 크기 제한도 있으니 대형 업로드는 업로드 직결(S3 등)로 보내고 서명만 API에서 처리하는 구조가 안정적이에요.
결론은 세 줄. 환경변수/캐시정책/런타임을 먼저 고정, 라우팅 규칙은 최소로 단순화, 무거운 건 분리. 이 세 가지만 잡아도 프리뷰에서 멀쩡한데 프로덕션에서만 망가지는 일, 확 줄어요.
정보
Vercel에 Next.js 프로젝트를 배포할 때 주의해야 할 설정이나 오류가 있다면 어떤 게 있나요?
Youth isn’t always all it’s touted to be. – Lawana Blackwell