はじめに
Cosoado Lab では Next.js 1 リポジトリから 4 つのNEXT_PUBLIC_GENRE という env var だけ
この
- 同じスタックで「色違い・
機能違い」の アプリを 横展開したい - でも DB や
コードを 4 倍持ちたくない - Vercel 公式
ドキュメントには「1 repo → 1 project」の 例しかなく、 複数紐付けの 運用例が 少ない
最後までgit push で 3 本番 + 1 staging が
構成の全体像
| プロジェクト | 環境変数 | ドメイン | 役割 |
|---|---|---|---|
| martial-matching | NEXT_PUBLIC_GENRE=martial | sparmate.cosoado-lab.com | 格闘技 |
| comedy-matching | NEXT_PUBLIC_GENRE=comedy | netapair.cosoado-lab.com | お笑い |
| boardgame-matching | NEXT_PUBLIC_GENRE=boardgame | boardlink.cosoado-lab.com | ボドゲ |
| matching-app-template | (未設定) | (preview のみ) | staging |
ソースは Next.js App Router + Supabase + Vercel という退屈な
手順
1. Vercel で同じ repo を複数プロジェクトに紐付ける
Vercel ダッシュボードで New Project → GitHub から
公式
2. プロジェクトごとに env var を切り替える
Vercel CLI で
npx vercel link --project martial-matching --yes
echo "martial" | npx vercel env add NEXT_PUBLIC_GENRE production
npx vercel link --project comedy-matching --yes
echo "comedy" | npx vercel env add NEXT_PUBLIC_GENRE production
npx vercel link --project boardgame-matching --yes
echo "boardgame" | npx vercel env add NEXT_PUBLIC_GENRE production
3. アプリ側で env var を読む
// src/config/genre.ts (記事用に 3 ジャンル抜粋。実テンプレは N ジャンル対応で追加可能)
type Genre = 'martial' | 'comedy' | 'boardgame';
export const GENRE_CONFIGS: Record<Genre, GenreConfig> = {
martial: {
name: 'SparMate',
primaryColor: '#dc2626',
defaultLanguage: 'en',
featureFlags: { locationRequired: true, superLikeEnabled: false },
},
comedy: {
name: 'NetaPair',
primaryColor: '#f97316',
defaultLanguage: 'ja',
featureFlags: { locationRequired: false, superLikeEnabled: true },
},
boardgame: {
name: 'BoardLink',
primaryColor: '#7c3aed',
defaultLanguage: 'ja',
featureFlags: { locationRequired: true, superLikeEnabled: false },
},
};
export function getGenreConfig(): GenreConfig {
const g = (process.env.NEXT_PUBLIC_GENRE ?? 'martial') as Genre;
return GENRE_CONFIGS[g];
}
NEXT_PUBLIC_ プレフィックスを
4. ドメインを各プロジェクトに割り当てる
各Settings → Domains で、CNAME を cname.vercel-dns.com に
やってみて初めて気づいた落とし穴 4 つ
先に
| # | 落とし穴 | 影響 | 対策 |
|---|---|---|---|
| 1 | Hobby の concurrent build = 1 | N プロジェクト | Pro 移行 or 緊急時のみ許容 |
| 2 | env var 設定漏れで | デフォルト | getGenreConfig で fail-fast |
| 3 | Build cache プロジェクト | 初回 N 倍の | 許容 or 自前構成移行 |
| 4 | 全 | PR 1 本で N プロジェクトが | vercel.json で deploymentEnabled 制御 |
落とし穴 1: Hobby プランの concurrent build は 1(=N 倍待つ)
これが
つまり 4 プロジェクトを
頻繁に push する
落とし穴 2: env var を設定し忘れて全部 same UI になる
これが'martial' で
対策はgetGenreConfig の
export function getGenreConfig(): GenreConfig {
const g = process.env.NEXT_PUBLIC_GENRE;
if (!g || !(g in GENRE_CONFIGS)) {
throw new Error(`NEXT_PUBLIC_GENRE is missing or invalid: "${g}"`);
}
return GENRE_CONFIGS[g as Genre];
}
これで env var 設定漏れがあれば
落とし穴 3: Build Cache はプロジェクト間で共有されない
同じソース・node_modules でも、プロジェクトごとに .next/cache は
回避策はnext start 系の
落とし穴 4: ブランチを切ると 4 プロジェクトすべてが preview をビルドする
落とし穴 1 とgit push origin feature/foo で 4 プロジェクト
対策は vercel.json の git.deploymentEnabled で、
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"git": {
"deploymentEnabled": {
"internal-*": false
}
}
}
上のinternal-* で
まとめ
- 同じ repo を Vercel の N プロジェクトに
紐付けると、 env var の 差だけで N 個の アプリを 同時運用できる - ただし Hobby プランの concurrent build は 1 なので、
ビルドは 直列になる。 push から 最後の 本番が 緑に なるまで N 倍待つ覚悟が 要る - build cache 非共有・
env var 設定漏れ・ の 3 つはpreview deploy の 連鎖 事前に 押さえる - Hobby の 1 repo あたりプロジェクト
上限は 10 なので、 設定上の 天井までは 余裕が ある
このNEXT_PUBLIC_GENRE 1 つで