
Cloud Run에 Next.js를 소스 배포했을 때 503 에러 발생 — 원인은 next.config.ts와 devDependencies였다
요약
Cloud Run의 소스 배포 시 Next.js 15 환경에서 발생하는 503 에러의 원인과 해결책을 다룹니다. Buildpacks가 실행 이미지 생성 시 devDependencies를 제거하면서, 기동 시 TypeScript가 필요한 next.config.ts를 처리하지 못해 발생하는 문제입니다.
핵심 포인트
- Cloud Run Buildpacks는 실행 이미지 생성 시 devDependencies를 삭제함
- next.config.ts는 실행 시점에 트랜스파일을 위해 typescript 패키지가 필요함
- 빌드는 성공하지만 런타임 기동 실패로 인해 503 에러가 발생함
- 해결책으로 next.config.ts를 next.config.mjs로 변환하는 것을 권장함
- Cloud Run의 소스 배포 (Buildpacks)는 빌드 후의 실행 이미지에서 devDependencies를 삭제합니다. 반면
next start
은 기동 시에 next.config.ts를 트랜스파일 (transpile)하기 위해 typescript를 필요로 합니다. 결과적으로, 빌드는 성공하지만 기동만 실패하여 503 에러가 발생합니다. 로컬에서는 절대 재현되지 않습니다. 대처법으로는 next.config.ts를 next.config.mjs (순수 JS)로 변환하는 것이 가장 빠르고 부작용이 없습니다.
다음 사항에 모두 해당한다면, 이 기사의 케이스일 가능성이 높습니다.
- Next.js 15 계열 (14에서도 동일)을 App Router로 사용 중
- 설정 파일이
next.config.ts(TypeScript 형식) typescript를 devDependencies에 넣음 (통상적인 방식)- Cloud Run에
gcloud run deploy --source .(Dockerfile 없는 소스 배포)를 하고 있음 - 배포는 성공했다고 표시되지만, 접속하면 503 에러가 발생함
배포 명령은 정상 종료되고, 리비전(revision)도 "serving 100 percent of traffic" 상태가 됩니다. 그런데도:
$ curl -s -o /dev/null -w "%{http_code}" https://my-app-xxxx.asia-northeast1.run.app/
503
로그를 확인하면 정체를 알 수 있습니다.
$ gcloud run services logs read my-app --region asia-northeast1 --limit 30
⨯ Failed to load next.config.ts, see more info here https://nextjs.org/docs/messages/next-config-error
[Error: Cannot find module 'typescript'
...
Next.js가 기특하게도 실행 시에 TypeScript를 설치하려고 시도하고 있지만, 제때 완료되지 못해 요청은 503 상태로 남았습니다.
소스 배포에서 사용되는 Google Cloud Buildpacks는 빌드 단계에서는 devDependencies를 포함한 모든 패키지를 설치하지만, 실행 이미지를 생성할 때 프로닝 (pruning, 불필요한 요소 제거)을 통해 프로덕션용으로 만듭니다. typescript는 통상적으로 devDependencies에 두기 때문에 실행 이미지에는 존재하지 않습니다.
이것 자체는 "실행 이미지를 가볍게 만들기 위한" 올바른 동작입니다.
next.config.ts는 빌드 시뿐만 아니라, next start 기동 시에도 읽히며 그때마다 트랜스파일됩니다. 이 트랜스파일에는 typescript 패키지가 필요합니다. 즉, "빌드 시에는 typescript가 있음 → 빌드 성공", "기동 시에는 typescript가 없음 → 기동 실패"라는 빌드 환경과 실행 환경의 차이가 그대로 사고로 이어집니다. 로컬 개발 환경에서는 devDependencies가 항상 포함되어 있기 때문에 이 문제를 알아차릴 수 없습니다. 운영 환경에서 처음 겪게 되는 유형의 함정입니다.
설정이 단순하다면, 순수 JavaScript로 변환하는 것이 가장 깔끔합니다.
변환 전 (next.config.ts):
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
eslint: { ignoreDuringBuilds: true },
...
변환 후 (next.config.mjs):
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: { ignoreDuringBuilds: true },
...
포인트:
@typeJSDoc 코멘트를 달아주면, 에디터의 자동 완성 및 타입 체크는 TS 시절과 거의 동등하게 작동합니다.next.config.ts는 삭제해 주세요. 두 파일이 모두 있으면.ts가 우선되어 의미가 없어집니다.
| 방법 | 장점 | 단점 |
|---|---|---|
next.config.mjs로 변환 | 부작용 없음 · 최단 시간 해결 | 설정 파일 내에서 TS(TypeScript)의 타입 기능을 최대한 활용하고 싶은 경우에는 부적합 |
typescript를 dependencies로 이동 | 코드 변경 없음 | 실행 이미지 크기가 수십 MB 커짐. "왜 운영 환경 의존성에 TS가 들어있지?"라는 미래의 혼란 초래 |
Dockerfile + standalone 출력 | 근본적 해결 · 이미지 최소화 | 소스 배포(Source Deploy)의 간편함을 포기해야 함 |
개인 개발이나 소규모 프로젝트라면 next.config.mjs가 최선이며, 이미지 크기나 빌드를 엄격하게 관리하고 싶은 팀이라면 standalone 방식을 검토하는 식으로 구분하여 사용하면 됩니다.
$ gcloud run deploy my-app --source . --region asia-northeast1 ...
$ curl -s -o /dev/null -w "%{http_code}" https://my-app-xxxx.asia-northeast1.run.app/
200
Vercel에서는 발생하지 않습니다. Vercel은 빌드와 실행 환경을 플랫폼 측에서 일치시켜 줍니다. 이 문제는 "Buildpacks 계열의 소스 배포 × next.config.ts"의 조합에서 발생하는 특수한 사례입니다. 같은 논리로, 기동 시에 로드되는 코드가 devDependencies에 의존하고 있지는 않은지는 소스 배포 전반에서 의식해 두면 사고를 줄일 수 있습니다. Cloud Run 트러블슈팅 시에는 먼저 gcloud run services logs read를 실행하는 습관을 들이면 "배포는 성공했는데 작동하지 않는" 계열의 원인을 즉시 파악할 수 있습니다.
Cloud Run 소스 배포 × Next.js 환경에서 "배포 성공 후 503 에러"가 발생한다면, 우선 로그에서 Cannot find module 'typescript'를 의심하십시오.
- 원인은 "Buildpacks가 실행 이미지에서
devDependencies를 제거함" × "next start가 기동 시에next.config.ts를 읽음"의 결합입니다. - 설정이 단순하다면
next.config.mjs로 변환하는 것이 가장 빠른 해결책입니다.
이 구성(Next.js + Supabase + Vertex AI Gemini + Cloud Run)으로 텃밭 가꾸기 AI 상담 앱인 "난토카나루 사이엔(なんとかなる菜園)"을 만들었습니다. 아키텍처 전체에 관한 이야기는 Zenn에 작성해 두었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기