1. なぜ ChatGPT App に Vercel を使うのか
前回までの講義では、GiftGenius をローカルで起動し、Dev Mode とトンネル経由で ChatGPT に接続しました。今回はさらに一歩進めて「本番運用」に近づけ、同じコードを Vercel に載せます。
この時点で、あなたの手元には動作する GiftGenius(学習用のサンプル App)があります。ローカルでは Next.js 16 上で MCP エンドポイント(例: /api/mcp)を提供し、公式の ChatGPT Apps SDK Next.js Starter に基づいて構築されています。
「VPS を借りて Node と nginx を手動で入れて自分で全部設定する」道を選ぶこともできますが、Next.js に対してそれは、2025 年に素の document.write でフロントエンドを書くようなものです。動きはしますが、わざわざ自分を苦しめる選択です。
Vercel が私たちに向いている理由はいくつもあります。
まず、Vercel は Next.js をネイティブに理解しています。ビルド、SSR、静的配信、Edge レイヤー、サーバーレス関数を自動で設定してくれます。ChatGPT App にとっては特に便利で、ウィジェットと MCP エンドポイントをワンクリックで同一インフラに展開できます。
次に、Vercel は CI/CD が標準搭載です。Git リポジトリを接続すると、各 push ごとにユニークな URL のイミュータブルなデプロイが作られます。main ブランチからのデプロイは production、それ以外は preview として扱われます。
さらに、Vercel は環境とシークレットの扱いが優秀です。環境変数を Development/Preview/Production に明確に分け、暗号化して保存し、Next.js に簡単に注入できます。ChatGPT App では、キーや MCP サーバーの URL を環境ごとに切り替える必要があるため、まさに理想的です。
そして、Vercel には便利なロールバックがあります。新しいリリースが不調でも、以前の成功したデプロイをすばやくプロモートして、システムを健全な状態に戻せます。これにより「デプロイの恐怖」が減り、小さな頻繁リリースが促進されます。
最後に、Vercel は Next.js の開発元です。彼らは Next.js と自社のサーバーを相互に最適化しています。Vercel を使うと、いかにスムーズに数クリックで物事が動くかを何度も体感するでしょう。きっと気に入るはずです。
2. 出発点: GiftGenius のプロジェクト構成
このコースの方針では、GiftGenius は 1 つのリポジトリにあります。構成は 2 通りあり、どちらも Vercel で問題ありません:
1) 複数アプリのモノレポ — 例:
giftgenius/
apps/
web/ # Next.js(ウィジェット + MCP)
mcp/ # 独立した MCP サーバー(もし切り出していれば)
2) 単一の Next.js プロジェクト(ウィジェットと MCP が同居。初期はこれが簡単で、公式スターターも同様):
giftgenius/
app/
page.tsx # ウィジェット
api/
mcp/route.ts # MCP エンドポイント
next.config.mjs
package.json
...
モジュール 2 の講義では Apps SDK Starter をクローンし、依存を入れて npm run dev を起動しました。ここでは次を前提とします:
- プロジェクトはすでに Git(GitHub / GitLab / Bitbucket)で管理されている;
- ローカルではキー(OPENAI_API_KEY など)を含む .env.local を使っている;
- ChatGPT の Dev Mode はあなたのトンネルに接続されている。
目標は、同じコードを Vercel でもビルド・動作させ、ChatGPT がトンネルではなく、https://giftgenius.vercel.app のような安定した HTTPS ドメインにアクセスできるようにすることです。
3. デプロイ前のリポジトリ準備
Vercel で「New Project」を押す前に、リポジトリを少し整えておきましょう。どれも簡単ですが、後で大きな時間節約になります。
まず、.env.local と .vercel がリポジトリに含まれないことを確認します。.gitignore には Next.js Starter で通常項目が入っていますが、念のため再確認を:
node_modules
.next
.env.local
.vercel
.env.local はローカルの設定とシークレットです。特に OPENAI_API_KEY や DB キーがあるなら、Git に入れてはいけません。Vercel 上では UI から別途シークレットを管理します。
次に、package.json を確認します。Vercel では正しい scripts が重要です:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
Vercel はデフォルトで npm run build(pnpm を使うなら pnpm build)を呼びます。これでエラーなくビルドできなければなりません。
さらに、Next.js 16 に適した Node のバージョンが明示されているか確認します。Next.js 16 のリリースノートでは最小が 18.18.0 です。多くの場合、package.json に次の指定で足ります:
{
"engines": {
"node": ">=18.18.0"
}
}
Vercel はアプリに互換な LTS の Node を選択します。
ここまでできたら、最新コードを Git に push して Vercel に進みましょう。
4. Vercel への初回インポート
ここから Vercel の Web インターフェースに進みます。まだ登録していない場合は、今がその時です。
Vercel にログインし「New Project」をクリック、リストから自分のリポジトリ giftgenius を選びます。この段階で Vercel はリポジトリ内容を解析し、ほぼ自動で Next.js プロジェクトだと判定して適切なプリセットを適用します。
プロジェクト設定では Vercel から次が提案されます:
- Framework = Next.js;
- Build Command = npm run build(または pnpm build/yarn build);
- Output Directory = 標準の .next(変更不要)。
初回デプロイでは環境変数をすぐに指定しなくても構いません(後で追加します)。「Deploy」を押すと、Vercel がリポジトリをクローンし、依存をインストール、npm run build を実行し、成功すれば https://giftgenius-xyz.vercel.app のようなアドレスで最初のデプロイが作成されます。
重要なポイント: 各デプロイはイミュータブルです。変更を push すると新しい URL の新しいデプロイが作られ、古いものは履歴に残ります。プロダクションドメイン(giftgenius.vercel.app やカスタムドメイン)は特定のデプロイを指し、ロールバックで以前のデプロイに切り替えられます。
概念図は次の通りです:
flowchart LR
A[GitHub repo
giftgenius] -->|git push| B[Vercel build]
B --> C[Preview Deploy #1
unique URL]
B --> D[Preview Deploy #2
unique URL]
D --> E[Production Alias
giftgenius.vercel.app]
Git の main ブランチは通常 production ブランチとして扱われ、それ以外は preview です。これは設定で変更できます。
5. Vercel の環境変数
初回デプロイは多くの場合まだ正常に動きません。OPENAI_API_KEY が未設定だったり、MCP サーバーが外部 API にアクセスできなかったりするためです。ここで環境変数を整えます。
Vercel の環境変数は Settings → Environment Variables で管理します。ここで Development、Preview、Production の 3 つのスコープに分かれているのが分かります。
理解の助けになる対応表:
| Scope | 使用場所 | ローカルの類推 |
|---|---|---|
| Development | vercel dev と Vercel CLI 経由のローカル開発 | .env.local |
| Preview | production ブランチ以外からのすべてのデプロイ | staging / test |
| Production | production ブランチ(通常は main)からのデプロイ | 本番相当の .env.prod |
ローカルの .env.local と違い、Vercel は値を暗号化して保存し、Next.js のコードからは process.env.MY_VAR として自動で参照できます。
プレフィックス NEXT_PUBLIC_ の意味を正しく理解しましょう。NEXT_PUBLIC_ で始まるものはブラウザのバンドルに含まれ、誰でも(DevTools で)見られます。これは公開設定(NEXT_PUBLIC_ENV=preview、NEXT_PUBLIC_API_BASE_URL=https://giftgenius.vercel.app など)には有用ですが、OPENAI_API_KEY のようなキーには厳禁です。
シークレットは NEXT_PUBLIC_ を付けず、サーバーサイド(route handler、MCP ツールなど)だけで読み込みます。
6. GiftGenius の環境変数設定例
学習用の GiftGenius に必要な環境変数を見てみましょう。
最小構成の例:
- OPENAI_API_KEY — モデル呼び出し/MCP クライアント用のキー;
- APP_BASE_URL — アプリのベース URL(https://giftgenius.vercel.app もしくは preview の URL);
- 必要に応じて GIFTDATA_API_URL や PRODUCTS_API_URL(外部カタログがある場合)。
ローカル開発では .env.local に置きます:
OPENAI_API_KEY=sk-local-...
APP_BASE_URL=http://localhost:3000
PRODUCTS_API_URL=https://dev-api.gifts.example.com
Vercel では Settings → Environment Variables に進み、同じキーと値を該当するスコープに追加します。
MCP エンドポイントのコード側の例:
// app/api/mcp/route.ts
import { NextRequest } from 'next/server';
const apiKey = process.env.OPENAI_API_KEY!; // 実コードでは未チェックのままにしないでください :)
export async function POST(req: NextRequest) {
if (!apiKey) {
return new Response('Missing OPENAI_API_KEY', { status: 500 });
}
// apiKey を使って OpenAI や他サービスを呼び出す...
}
ウィジェットはサーバーサイドで APP_BASE_URL を使い、ChatGPT の iframe やスターターの assetPrefix/basePath 設定を考慮して絶対 URL を組み立てられます。
クライアントからの window.fetch でバックエンドの公開 API を叩く必要があれば、NEXT_PUBLIC_API_BASE_URL を用意しても良いでしょう。ただし NEXT_PUBLIC_OPENAI_API_KEY は決して設定してはいけません。
7. Preview デプロイ: 強力なステージング
ここからは一番楽しい preview デプロイの話です。Git リポジトリを接続すると、Vercel は production 以外のブランチへの push や各 Pull Request ごとに自動で preview デプロイを作成します。各デプロイには次のようなユニーク URL が付きます:
https://giftgenius-git-feature-new-layout-username.vercel.app
これらのデプロイは env の Preview スコープを使うため、例えば次のように分けられます:
# Vercel の Preview 環境変数
APP_BASE_URL=https://giftgenius-staging.vercel.app
PRODUCTS_API_URL=https://staging-api.gifts.example.com
これで production と取り違えることがありません。
ChatGPT の Dev Mode 的には、preview URL はステージングに最適です。Dev App の設定で、トンネルの URL を一時的に preview URL に変え、すでにビルドされた GiftGenius の挙動(ただしまだ production ではない)を確認できます。
よくある流れ: 機能開発用に feature/smart-recommendations ブランチを作って push → Vercel が preview リンクを発行。Dev Mode に行き、その URL を設定して、GPT とのシナリオ(ギフト選定、カード表示、MCP ツール呼び出し)を確認。問題なければ main にマージ。Production はその間、安定稼働を続けます。
パイプラインのメンタルモデル:
flowchart TD
A[Local dev
localhost + トンネル] --> B[git push
feature/*]
B --> C[Preview Deploy
preview-URL]
C --> D[ChatGPT Dev Mode
App → preview-URL]
C --> E[コードレビュー / テスト]
E --> F[main にマージ]
F --> G[Production Deploy
prod-URL]
G --> H[ChatGPT Prod App
App → prod-URL]
8. Production デプロイとロールバック
main(または設定した production ブランチ)にマージすると、Vercel は production デプロイを作り、giftgenius.vercel.app やカスタムドメインといった production エイリアスを割り当てます。
この時点で(後ほど作成する)ChatGPT の Prod App は production の URL をエンドポイントに設定しておきます。Dev Mode では引き続きトンネルや preview URL を使って実験し、ChatGPT Store の一般ユーザーは production にアクセスするイメージです。
イミュータブルデプロイの利点は、ロールバックが非常に簡単なことです。新しいリリースが不調(たとえば本番データで MCP ツールが落ちる)でも、慌てて本番で直す必要はありません。Vercel のデプロイ一覧から以前の成功デプロイを選び「Promote to Production」のような操作をすると、遠くの K8s や Lambda が切り替わり、ドメインは安定版を指すようになります。
CLI でも vercel rollback のようなコマンドで自動化できますが、本講義の範囲では「各デプロイは独立したアーティファクトで、production エイリアスはどれにでも向け直せる」という理解で十分です。
9. Vercel 上の Next.js 16 + MCP の特性
Vercel から見た Next.js の MCP エンドポイントは、サーバーレス関数(設定によっては Edge 関数)です。これは短命で、リクエストで起動し、処理して終了します。外部の DB やストレージを使わない限り、呼び出し間で状態を保持できません。
これは MCP にとって致命的です。もし route.ts にあるグローバル配列 let history = [] に対話履歴を保存すると、コールドスタートのたびに初期化されます。状態を保持するには外部システム(KV、Postgres など)が必要ですが、その話は後のモジュールで扱います。
もう 1 つは実行タイムアウトです。無料プランの Vercel ではサーバーレス関数の実行時間に制限があります(本資料の作成時点では Hobby で約 10 秒、Pro はより長い)。LLM リクエストや特に MCP ツールのチェーンでは不足することがあります。
Next.js 16 の route handler では、Vercel に対してより長い実行時間を要求するために maxDuration を指定できます(プランの範囲内):
// app/api/mcp/route.ts
export const maxDuration = 60; // 秒。Pro では最大 300 まで
export async function POST(req: Request) {
// 時間のかかる処理: OpenAI や外部 DB へのリクエストなど
}
これは「無制限に動く魔法のボタン」ではありませんが、「この関数は長めに動く可能性があるので、早まって殺さないで」と Vercel に正しく伝える方法です。
最後に、ChatGPT の iframe の特性を忘れないでください。Apps SDK Starter ではすでに assetPrefix と basePath が設定され、web-sandbox.oaiusercontent.com のネストした iframe 内でも静的ファイルやルーティングが正しく動くようにしてあります。これにより、リクエストは sandbox ではなくあなたのドメインに向きます。Vercel にデプロイしてもこの設定は維持され、ウィジェットは箱から出してすぐに正しく動作します。
10. デプロイ後の ChatGPT 連携
形式的には Store や本番運用のモジュール寄りの話ですが、デプロイ後のアプリの動かし方・ChatGPT との連携ロジックは単純で、今の段階でも理解しやすいです。
まず GiftGenius を Vercel にデプロイして production の URL を得ます。次に ChatGPT の Dev Mode で別の App(例: GiftGenius Prod)を作り、その設定でエンドポイントとしてこの URL(正確にはガイドに沿って https://giftgenius.vercel.app/api/mcp のような MCP エンドポイント)を指定します。
開発では引き続き Dev App を使い、トンネルや preview URL を向けます。日次/週次ビルドの検証用に Staging App を作り、安定した preview エイリアスに紐づけるのも良いでしょう。結果として 3 段構えになります:
Dev App → ローカルトンネル または dev-URL(不安定)
Staging App → 安定した preview/staging URL(Vercel)
Prod App → production URL(Vercel)
目安として、次の表にまとめます:
| 対象 | URL / Vercel デプロイ | Vercel のスコープ | アクセスする人 |
|---|---|---|---|
| Dev App | ローカルトンネル / vercel dev | Development | あなた / チーム |
| Staging App | 安定した preview エイリアス | Preview | チーム / QA |
| Prod App | giftgenius.vercel.app / カスタムドメイン | Production | ユーザー |
これは冒頭で述べた local / staging / prod モデルを、Vercel と ChatGPT Apps に結びつけたものです。もはや永遠の localhost ではなく、ちゃんとしたプロジェクトのアーキテクチャになっています。
11. Vercel デプロイでのよくあるミス
エラー No.1: .env.local にだけシークレットがあり、Vercel に設定していない。
ローカルでは動くので自信を持って「Deploy」を押したら、アプリはビルドできたが本番で MCP ツールが 500「Missing OPENAI_API_KEY」を返す——よくある話です。原因は単純で、Vercel はあなたのローカル .env.local を知りません。同じ変数を Vercel プロジェクト設定に(そして正しいスコープ Preview/Production に)登録する必要があります。
エラー No.2: 機微情報に NEXT_PUBLIC_ を使ってしまう。
「とにかく動かしたい」という思いから、クライアント側でキーにアクセスするために NEXT_PUBLIC_OPENAI_API_KEY を書いてしまうことがあります。結果、そのキーは JS バンドルに入り、誰でも取得できます。これは悪手というレベルではなく、漏洩とキー停止への最短経路です。すべてのシークレットはプレフィックスなし・サーバーサイドのみで使用しましょう。
エラー No.3: ローカルと Vercel の環境が不整合。
ローカルではプロダクトの URL が http://localhost:4000、Vercel の Preview では https://api.gifts-staging.com、本番では別の URL、という具合にバラバラになりがちです。必要な環境変数のリストを整備せず、Preview/Production で正しく埋まっているか点検しないと、「本番ウィジェットが staging バックエンドに、staging ウィジェットが本番に」という入れ違いが起きます。単純な対策ですが、変数を文書化し、各環境でチェックする習慣が効きます。
エラー No.4: MCP エンドポイントの実行時間制限を無視。
ローカルでは遅い外部システムの応答を 30 秒待っても問題に気づかないことがあります。Vercel では同じ関数が 10〜15 秒でタイムアウトし、ChatGPT はエラーを受け取ります。maxDuration を設定せず、MCP ツールの実行時間を監視していないと、本番で断続的な障害になります。
エラー No.5: MCP の状態をサーバーレス関数のメモリに保持しようとする。
対話履歴や推薦キャッシュを route handler のファイル内のグローバル変数 let cache = {} に置きたくなることがあります。ローカルでは dev サーバーが長く動いていて「動いているように見える」かもしれません。しかし Vercel では各サーバーレス関数は短命で頻繁に作り直されます。結果、あるリクエストは古い cache、別のリクエストは新しい cache、さらに別のリクエストは空の cache を見る、というカオスになります。再現しにくい厄介なバグの温床です。状態には外部 DB や KV ストアを使い、この講義のレベルでは MCP エンドポイントは stateless と考えるのが無難です。
GO TO FULL VERSION