CodeGym /コース /ChatGPT Apps /デプロイでありがちなミスとそのデバッグ戦略

デプロイでありがちなミスとそのデバッグ戦略

ChatGPT Apps
レベル 7 , レッスン 4
使用可能

1. デプロイの解剖学:どこで壊れ得るのか

まずは全体のチェーンを可視化すると有益です。あなたのアーキテクチャにおける ChatGPT App のデプロイは、頭の中で次のような直線に展開できます。

flowchart TD
  A[あなたのノートPC
git commit] --> B[Git リポジトリ
GitHub/GitLab] B --> C[Vercel Build
npm run build] C --> D[Vercel Deploy
Preview/Prod] D --> E[HTTP エンドポイント
/mcp, /api/...] E --> F[ChatGPT / Dev Mode
ツール呼び出し、ウィジェット]

エラーはこのどの段階でも発生し得ますが、ChatGPT 側の症状はだいたい似ています: "Error talking to app""Network error"、もしくは無反応。 やみくもに対処するのではなく、まず「ビルド時に落ちたのか、実行時に落ちたのか、あるいは ChatGPT の参照先が違うのか」を見極めることが重要です。

問題は大きく3つに分けると整理しやすいです。

  • ビルドエラー: Vercel がプロジェクトをそもそもビルドできない。本番は更新されない(それ自体は「安全」)ものの、ビルドは赤(失敗)。
  • ランタイムエラー: ビルドは通るが、リクエスト時に 500/502、タイムアウト、または奇妙な挙動が出る。
  • コンフィグドリフト(構成のずれ): ローカルでは正常、Vercel のログ上も正常だが、ChatGPT が古い URL・古いマニフェスト・空の環境変数で動いている。

この3層を順に見ていきつつ、共通のデバッグ戦略を組み立てます。

2. ビルドエラー:プロジェクトがビルドできないとき

最初のタイプは導入で述べた「ビルドエラー」です。Vercel が Next.js プロジェクトのビルドに失敗し、そもそもアプリが組み上がりません。

Node と Next.js:環境が違えば要件も違う

ローカルでは古い Node を(残念ながら)使えてしまいますが、Vercel はサポート範囲の Node で Next.js 16 プロジェクトをビルドしようとします (最低でも 18.18.0)。 package.json に非互換なバージョンを明示していると、dev サーバーは動いていたのに本番ビルドで落ちる、ということが起きます。

対策の一つは、package.json"engines" を明示することです。

{
  "engines": {
    "node": ">=18.18.0"
  }
}

これでローカルでも CI/Vercel でも、Node が古すぎることを事前に検知できます。

「自分の環境では動く!」と忘れられた依存関係

典型例:npm install some-lib はしたが、更新された package-lock.json を commit していない、あるいは一部依存をグローバルに入れている。 Vercel は「ゼロ」からアプリを組み立て、マニフェストに基づいて npm install を実行しますが、 そこにお気に入りの some-lib がない——結果、ビルドエラーです。

有効なのは「厳格な運用」です。

  • 新しい依存を追加したら即 commit する;
  • main/production に push する前にローカルで npm run build を回す。ローカルの build が落ちるなら、Vercel ではなおさら悪化します。

大文字小文字を区別するファイルシステム

ローカルでは多くの人が macOS や Windows を使っており、標準ではファイル名の大文字小文字を区別しません。 Vercel のビルドは Linux 環境なので、Widget.tsxwidget.tsx は別物です。

よくあるバグ:

// コード内のインポート
import { AppWidget } from "@/components/Widget";

// しかしリポジトリには components/widget.tsx がある

手元では動くのに、Vercel では「Cannot find module '@/components/Widget'」のモジュールエラー。 ファイル名の統一と大文字小文字への注意で解決します。

ビルド時の環境変数

もう一つの驚きの種は、ビルド段階で実行されるコード(たとえば next.config.mjs や build 時に import されるモジュール)内で process.env.* を使うことです。 ローカルでは .env.local を読み込んでいるのに、Vercel の build 環境に同じ変数を用意し忘れると、 ビルドが落ちるか、もっと厄介には undefined のまま「焼き付いて」、不正な値がバンドルに入ります。

ChatGPT App では、たとえば MCP エンドポイントの baseURL や外部 API の URL をビルド段階で組み立てている場合に特に致命的です。

良いプラクティスは、アプリ起動前(より正確には import 時)に重要な環境変数を明示的に検証して、 ビルドを大きくはっきりと失敗させることです(この点は別セクションでも触れます)。

3. ランタイムエラー:ビルドは通ったのに動かないとき

次は2つ目の層、ランタイムエラーです。ビルドは通るが、実行時に壊れます。

ビルドが通り、Vercel は緑のデプロイを示し、ChatGPT App を本番 URL に切り替えた——ところがチャットでは "Error talking to app"。 つまり、問題は実行時レベルに移りました。

null/空の環境変数

ChatGPT App の本番インシデントは、たいてい undefined から始まります。 手元の .env.localOPENAI_API_KEYMCP_BASE_URL などがきれいに入っているのに、 Vercel では用意し忘れたり名前を間違えたりしがちです。

例えば次のように読んでいるのに:

const apiKey = process.env.OPENAI_API_KEY;

Vercel 側では OPENAI_APIKEYOPENAI_API_KEY_PROD を登録してしまっている。 その結果、最初の MCP ツール呼び出しで route handler が認証エラーで落ちます。

望ましいのは、アプリがすぐに、そしてわかりやすく落ちること。Next.js プロジェクトに 環境変数を import 時に検証する専用モジュールを用意するのが良いパターンです。

// app/lib/env.ts
const required = ["OPENAI_API_KEY", "MCP_BASE_URL"] as const;

type RequiredKey = (typeof required)[number];

function getEnv(key: RequiredKey): string {
  const value = process.env[key];
  if (!value) {
    throw new Error(`Missing required env var: ${key}`);
  }
  return value;
}

export const env = {
  OPENAI_API_KEY: getEnv("OPENAI_API_KEY"),
  MCP_BASE_URL: getEnv("MCP_BASE_URL"),
};

これで、Vercel 側の設定を忘れていると、env を最初に import した時点で Next.js が落ち、 ログに "Missing required env var: ..." という人間にわかるメッセージが残ります。

また、Vercel では環境変数の変更は自動では反映されません。 値を変更したら新しいデプロイ(redeploy)が必要です。そうしないとランタイムは古い値のまま生き続けます。

route handler と MCP エンドポイントのエラー

公式の ChatGPT App テンプレートでは、MCP サーバーは通常 app/mcp/route.ts として実装されています。 内部では JSON‑RPC リクエストをパースし、ツールにルーティングして応答を返します。 チェーンのどこかで throw を握りつぶさないままにすると、ChatGPT 上のユーザーには 500 が返ります。

MCP ハンドラの最上位は常に try/catch で包み、エラーをログに出し、構造化した応答を返しましょう。

// app/mcp/route.ts
import { NextRequest, NextResponse } from "next/server";

export const dynamic = "force-dynamic";
export const maxDuration = 30; // 秒

export async function POST(req: NextRequest) {
  try {
    const body = await req.json();
    // ここで MCP リクエストを処理
    const result = await handleMcpRequest(body);
    return NextResponse.json(result);
  } catch (error) {
    console.error("MCP route error", error);
    return NextResponse.json(
      { error: "Internal MCP error" },
      { status: 500 }
    );
  }
}

ポイント:

  • dynamic = "force-dynamic" は、Next.js 16 における MCP ルートの予期せぬ静的生成やキャッシュを避けるのに役立ちます。
  • maxDuration = 30 は、route handler が最大 30 秒まで動作し得ることを Vercel に明示します。長い LLM 呼び出しでは重要です。

タイムアウトと ChatGPT の「Network error」

Vercel の serverless 関数の実行時間には上限があります。無料プランではおおよそ 10 秒、 有料ではより長く(数分まで)なります。 MCP ツールが DB や外部 API へ長いリクエストを行うと、間に合わず、 ChatGPT では "Network error" や途中で途切れたストリームになります。

ストリーミング(SSE)で部分結果を返すときは、タイムアウト前に最初のバイトを返すことが特に重要です。 そうすれば転送自体は長くても、プラットフォームは関数を「ハングした」と見なしません。

小技として、ツール呼び出しの所要時間を計測してツール名と一緒にログに出しましょう。 ログから、例えば search_flights が一貫して 12 秒かかっており、 制限にわずかに収まらない、といった事実が見えます。

export async function safeToolCall<TInput, TOutput>(
  name: string,
  handler: (input: TInput) => Promise<TOutput>,
  input: TInput
): Promise<TOutput> {
  const started = Date.now();
  try {
    const result = await handler(input);
    console.log("[tool] ok", name, { ms: Date.now() - started });
    return result;
  } catch (error) {
    console.error("[tool] fail", name, {
      ms: Date.now() - started,
      error,
    });
    throw error;
  }
}

以後は handler(args) の代わりに safeToolCall("search_flights", handler, args) を呼びます。

ネットワークと外部サービス

原因が単純に https://http:// の取り違えや、古い baseURL だったりすることもあります。 ローカルである URL で先に試して、その後本番では別ドメインや別ポート、という状況では特に起きやすいです。

基本の URL は(環境ごとの)設定に切り出し、ツールのコードに直接ハードコードしないのが有益です。 そうすれば環境を変えるときに env を1つ更新するだけで済み、コードの複数箇所を思い出して直す必要がありません (例:http://localhost:3001)。

4. 構成と環境ドリフト

最後に、3つ目のタイプは環境間の構成ドリフトです。

ビルドが通り、ランタイムのログも健康そうに見えるのに、ChatGPT 上では「別バージョンのアプリが動いているように」見えることがあります。 これはコードというより、設定と環境間の整合性の問題です。

Dev Mode と本番の違い

Dev Mode の ChatGPT は、手動で指定した Connector URL を参照します。多くはトンネル URL (https://myapp-dev.ngrok-free.app/mcp のようなもの) や Vercel の staging URL です。 本番(Store や組織設定経由)では、安定した本番エンドポイント、例えば https://myapp.vercel.app/mcp を指す必要があります。

ほぼ全員が一度はやるミス:Vercel にデプロイしたのに、ChatGPT App の設定がまだ古いトンネル URL を向いている。 ローカルサーバーは停止、トンネルは死んでいるのに、ChatGPT はそこにアクセスして 502。 UI 上は "Error talking to app" と見え、実際には実行されていない MCP コードを直し始めてしまいます。

対策は運用ルール:環境を変えたら(トンネル → staging、staging → prod など)、 Dev Mode と本番 App 設定で、どの URL が入っているかを必ず確認すること。

古いマニフェストと ChatGPT のキャッシュ

ChatGPT はあなたの App の情報をキャッシュします。 ツール一覧、説明、メタデータなどです。 そのため「スキーマを変えたのに、モデルは昔の引数名だと思っている」という状況は現実に起こります。

大きな変更のときに有効なのは次の通りです。

  • 本当に新バージョンをデプロイしたかの確認(ログで commit hash を見る、起動ログに出す)。
  • Dev Mode の App を作り直す/再接続して、プラットフォームにマニフェストの再読み込みを強制する。
  • デバッグ中は MCP Inspector 経由で作業し、最新のツール一覧とスキーマを確実に確認する。

環境変数の構成: dev/staging/prod

環境変数がビルドやランタイムを壊す話はすでにしました。 ここでは dev/staging/prod を俯瞰し、それらの間で値を揃える視点です。

よくある痛み:手元の .env.local は完璧なのに、Vercel の各環境はカオス。結果として:

  • ローカルはある API キーと外部サービスの URL;
  • staging はまったく別の値;
  • prod は変数の半分が未設定。

リポジトリにシンプルなテキスト docs/env.md を置いて、 どの変数が必要か、どの環境で必須か、サンプル値は何かを列挙すると非常に助けになります。 官僚的に見えますが、インシデントの瞬間に数時間を節約します。

5. ChatGPT 側から見たときのエラーの姿

次に、ChatGPT のユーザーの視点で状況を見てみましょう。 ユーザーは UI しか見えず、Vercel や Node、MCP の内情は知りません。 そして残念ながら、あなたもまだ何が壊れたのかはわかりません。

ChatGPT での典型的な症状:

  • 使用しようとするとすぐに "Error talking to [App Name]" のメッセージ;
  • 永遠に回り続けるスピナー(見えるエラーなし);
  • 赤文字で "I encountered an error while running the tool";
  • ウィジェットが出ない、または空で出る。

これらの症状は、たいてい特定のレベルの故障に対応しています。

  • App がそもそも到達不能(URL 違い、トンネル落ち、SSL エラー)なら、ChatGPT は MCP エンドポイントに届きません—— ブラウザでドメインの到達性と、Vercel のログ(4xx/5xx)を確認してください。
  • MCP が error フィールド付きの妥当な JSON‑RPC を返していれば、 ChatGPT はツールがエラーを返したことを正直に示します——これはビジネスロジックや引数のバリデーションの問題です。
  • MCP は成功を返したのに、ウィジェットの HTML が壊れている/JS エラーがあるなら、 ウィジェットのコンソール(DevTools → iframe)に何が落ちたかが出ます。

したがって良い習慣は、チャットで妙な挙動を見たらすぐに時刻(分単位まで)を控え、 その時間の Vercel ログで該当リクエストを探しに行くことです。

6. デバッグ戦略:慌てず行動するために

ここまでを踏まえ、何か起きたときの小さな「プレイブック」をまとめます。 目的は、堂々巡りをやめて落ち着いたアルゴリズムに置き換えることです。

ステップ1:問題の種類を特定する

Vercel のビルドが赤なら喜びましょう:本番前に捕まえています。 ビルドログを開き、(200 行の warning ではなく)最初の実エラーを見つけ、 ローカルで npm run build で再現します。

ビルドが緑で ChatGPT が文句を言っているなら、ランタイムかコンフィグです。次を確認します。

  • あなたの App の本番 URL にブラウザから到達できるか(https://myapp.vercel.app/mcp が何か返すか)。
  • MCP エンドポイントが 200/500 を返しているか、そもそも解決できないか。
  • App 設定の URL が、今しがた確認した URL と一致しているか。

ステップ2:勘ではなくログを読む

次の寄り道は Vercel のログです。目的のデプロイ・目的の環境(Preview/Production)のサーバーログを見ます。

探すもの:

  • Error: Missing required env var ... のようなエラー——コンフィグ問題です。
  • MCP ハンドラのスタックトレース——ビジネスロジックか入力データのパースが落ちています。
  • タイムアウトや関数実行時間超過のメッセージ。

あわせて MCP Inspector も忘れずに。 同じ MCP エンドポイントにインスペクタから接続して手動でツールを呼べば、 問題が MCP 自体にあるのか、ChatGPT ↔ MCP の接続にあるのかがすぐにわかります。

ステップ3:迅速なロールバックか、ホットフィックスか?

本番デプロイが明らかに壊れており(例えば MCP ルートが毎回同じエラーで落ちる)、 前のデプロイは健全だったなら、正解はロールバックです。 Vercel は、再ビルドなしで直前の成功デプロイに素早く切り替えられます——実質的にはアクティブバージョンの付け替えです。

根本原因が見えていない状態で「本番をその場で修理」しようとするより安全です。

状況を安定させたら、落ち着いて原因を特定し、テストを書き、修正して、 次のバージョンとしてリリースしましょう。

ステップ4:学びをドキュメントに定着させる

重大インシデントは、内部 README を更新する好機です。

  • それが無いと落ちる必須の環境変数をリストに追加する。
  • どのケースがエラーにつながったかを記録する(例:「ファイル名の大文字小文字を間違えた import」)。
  • 素早く復旧できた手順を簡潔に記す。

地味ですが、数カ月後の自分に感謝されます。

7. コードでの小さな実践テクニック

プレイブックのいくつかのステップを、学習用アプリ(ChatGPT App)の小さなコードテクニックとして固めましょう。

単一の設定モジュール

すでに簡単な環境変数バリデータを書きました。これを発展させて環境を区別しましょう。

// app/lib/config.ts
type NodeEnv = "development" | "test" | "production";

const nodeEnv = (process.env.NODE_ENV || "development") as NodeEnv;

const requiredBase = ["OPENAI_API_KEY"] as const;
const requiredProd = ["MCP_BASE_URL"] as const;

function ensure(keys: readonly string[]) {
  for (const key of keys) {
    if (!process.env[key]) {
      throw new Error(`Missing env var ${key} for NODE_ENV=${nodeEnv}`);
    }
  }
}

ensure(requiredBase);
if (nodeEnv === "production") {
  ensure(requiredProd);
}

export const config = {
  nodeEnv,
  openaiApiKey: process.env.OPENAI_API_KEY!,
  mcpBaseUrl: process.env.MCP_BASE_URL ?? "http://localhost:3000/mcp",
};

このモジュールにより、本番が必要な変数なしで起動している場合に即座に検知できます。

受信した MCP リクエストのロギング

シンプルですが非常に有用な MCP ハンドラ用ラッパーです。

// app/lib/mcp-logger.ts
export function logMcpRequest(body: unknown) {
  console.log("[mcp] request", {
    time: new Date().toISOString(),
    // 機微情報はログに出さない
    keys: typeof body === "object" && body !== null
      ? Object.keys(body as Record<string, unknown>)
      : typeof body,
  });
}

app/mcp/route.ts での使い方:

import { logMcpRequest } from "@/app/lib/mcp-logger";

export async function POST(req: NextRequest) {
  try {
    const body = await req.json();
    logMcpRequest(body);
    const result = await handleMcpRequest(body);
    return NextResponse.json(result);
  } catch (error) {
    console.error("MCP route error", error);
    return NextResponse.json({ error: "Internal error" }, { status: 500 });
  }
}

ログで、ChatGPT から何が来ているか(少なくともキー ("jsonrpc""method""params")) が見え、どの呼び出しが落ちているのか把握しやすくなります。

MCP エンドポイントの簡易ヘルスチェック

ChatGPT が直接呼ぶわけではありませんが、MCP サーバー用に簡単な「healthcheck」ルートを用意すると便利です。 ブラウザですぐに開いて、サーバーが生きているか、環境変数が見えているかを確認できます。

// app/api/health/route.ts
import { NextResponse } from "next/server";
import { config } from "@/app/lib/config";

export async function GET() {
  return NextResponse.json({
    status: "ok",
    env: config.nodeEnv,
    hasOpenAiKey: !!config.openaiApiKey,
  });
}

https://myapp.vercel.app/api/healthstatus: "ok" を返すなら、 少なくとも Node コードまでの基本パイプラインは生きています。

8. デプロイとデバッグでよくあるミス

誤り1:ローカルで npm run build をせずにデプロイ
開発者がローカルで一度も build を走らせないと、非互換 Node、パス問題、TS エラーを Vercel で初めて知ることになります。 そのたびに新しいデプロイが必要になり、「壊した → 直した」のサイクルが伸びます。 main に push する前に npm run build を回す習慣は時間を大きく節約します(セクション2、および 6.1 のローカル npm run build 参照)。

誤り2:秘密情報が .env.local にしかない
作者のマシンでは完璧に動くのに、本番では process.env.OPENAI_API_KEY === undefined で落ちる。 理由は単純:Vercel の設定に環境変数を追加し忘れた(そして別名で作ってしまうことも)。 Development/Preview/Production の分離を忘れ、staging と prod の挙動が違うことに驚くケースが多発します(詳細はセクション 3.1、4.3、7.1)。

誤り3:秘密情報に NEXT_PUBLIC_* を使う
Next.js では NEXT_PUBLIC_ プレフィックス付きの変数はブラウザバンドルに入ります。 誤って API キーを NEXT_PUBLIC_OPENAI_API_KEY と名付けると、 それはユーザーのブラウザへ渡り、DevTools から引き抜かれ得ます。これは厳禁。 公開してよいのは安全な値(例:フィーチャーフラグの ID など)だけです。

誤り4:Vercel のログを無視し、「ChatGPT で直す」
チャットで "Error talking to app" を見て、プロンプトやツール説明を何時間もいじり、 Dev Mode をあれこれ調整するのに、serverless のログを一度も見ないことがあります。 そこにはわかりやすいエラー——"Missing env var""Cannot find module"、 あるいは特定ツールのスタックトレース——が出ているのに。良いエンジニアはまずログを見てから、モデルと議論します。

誤り5:Dev Mode と本番 App を取り違える
最初の Vercel デプロイが成功すると、Dev Mode がまだ古いトンネルや preview URL を向いていることを忘れがちです。 結果として本番をテストしているつもりが、実は消すべきローカルブランチと話している、ということも。 逆に、下書きの修正を試しているつもりが、ChatGPT は本番エンドポイントにアクセスしている場合もあります。 App と Dev Mode の設定で URL が何かを定期的に確認しましょう(セクション 4.1 参照)。

誤り6:Vercel の環境変数の変更が「その場で」効くと思う
一部の受講生は Vercel のパネルで変数を変えると、すぐに ChatGPT で結果を見に行きます。 しかしランタイムはまだ古い値を使っています。なぜなら redeploy していないから。 環境変数の変更は常に新しいデプロイを要します。そうでないと関数は更新を見ません(詳細はセクション 3.1)。

誤り7:単純なロールバック戦略がない
インシデント時には、main に「素早い修正」を直 push したくなります。 しかしそれは、さらに壊れたデプロイを一つ追加するリスクであり、その間ユーザーは苦しみます。 深刻なエラーでは、直前の成功デプロイに即ロールバックし、別ブランチで修正してから新バージョンを出す方がはるかに穏当です。 Vercel にはそのための便利な UI があるのですから、活用しない手はありません。

1
アンケート/クイズ
デバッグとデプロイ、レベル 7、レッスン 4
使用不可
デバッグとデプロイ
環境、デバッグとデプロイ(Vercel + トンネル)
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION