TL;DR
- Cloud Run のソースデプロイ(Buildpacks)は、ビルド後の実行イメージから devDependencies を削除する
- 一方で
next startは、起動時にnext.config.tsをトランスパイルするために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
症状
デプロイコマンドは正常終了し、リビジョンも「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'
Require stack:
- /workspace/node_modules/next/dist/build/next-config-ts/transpile-config.js
...
⚠ Installing TypeScript as it was not found while loading "next.config.ts".
Next.js が健気にも実行時に TypeScript をインストールしようとしていますが、間に合わず、リクエストは 503 のままでした。
原因: 2つの仕様の合わせ技
1. Buildpacks は実行イメージから devDependencies を削除する
ソースデプロイで使われる Google Cloud Buildpacks は、ビルド段階では devDependencies 込みの全部入りをインストールしますが、実行イメージの作成時に本番用へプルーニングします。typescript は通常 devDependencies に置くので、実行イメージには存在しません。
これ自体は「実行イメージを軽くする」ための正しい挙動です。
2. next start は起動時に next.config.ts を読む
next.config.ts はビルド時だけでなく、next start の起動時にも読み込まれ、そのたびにトランスパイルされます。このトランスパイルに typescript パッケージが必要です。
つまり「ビルド時には typescript がある → ビルド成功」「起動時には typescript がない → 起動失敗」という、ビルド環境と実行環境の差がそのまま事故になります。ローカル開発では devDependencies が常に入っているため、この問題には気づけません。本番で初めて踏むタイプの罠です。
対処法
おすすめ: next.config.mjs に変換する
設定がシンプルなら、素のJavaScriptに変換するのが一番きれいです。
変換前(next.config.ts):
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
eslint: { ignoreDuringBuilds: true },
};
export default nextConfig;
変換後(next.config.mjs):
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: { ignoreDuringBuilds: true },
};
export default nextConfig;
ポイント:
-
@typeの JSDoc コメントを付ければ、エディタの補完・型チェックは TS 時代とほぼ同等に効きます -
next.config.tsは削除してください。両方あると.tsが優先されて意味がなくなります
別解との比較
| 方法 | メリット | デメリット |
|---|---|---|
| next.config.mjs に変換 | 副作用なし・最短 | 設定ファイル内でTSの型機能をフルに使いたい場合は不向き |
typescript を dependencies に移す |
コード変更なし | 実行イメージが数十MB太る。「なぜ本番依存にTSが?」という将来の混乱のもと |
| Dockerfile + standalone 出力 | 根本解決・イメージ最小化 | ソースデプロイの手軽さを捨てることになる |
個人開発や小規模プロジェクトなら 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に書いています。