既存の SaaS を supastack へ移行するには?
コードベースによりますが、順序は変わりません:一括リライトではなく段階的に移行します。リポジトリをプロダクトコード(ルート、スキーマ、ドメインロジック — あなたのもののまま)とインフラ(認証、テナンシー、課金、ジョブ、メール — キットが置き換える部分)に棚卸しし、依存関係の順序でモジュールを採用します:構造、認証、ワークスペース、課金、そしてバックグラウンド処理。データは再実行可能なスクリプトで移し、Stripe 顧客 ID を維持し、受け入れが通ってから切り替えます。ほとんどの Web アプリは数週間ではなく数日分の夜で済みます。
コードに触れる前に棚卸しをする
失敗するマイグレーションはすべて同じ始まり方をします:誰かがキットのリポジトリを開いてファイルを消し始めるのです。キットはテーマではなく基盤です — あなたのインフラを置き換えるものであり、あなたのプロダクトについて何の意見も持ちません。何かを動かす前に、リポジトリを正直に 2 つの山に分けてください。
プロダクトコードとは、顧客があなたに説明し返せるすべてのものです:ルート、スキーマ、ドメインロジック、コピー、料金ページ。インフラとは、彼らが決して見ないすべてのものです:セッション、テナンシーチェック、決済 Webhook、キューに入ったジョブ、トランザクションメール。1 つ目の山はそのまま移行します。2 つ目の山は置き換えられます — それがそもそもの要点です。
- すべてのルートグループを列挙し、プロダクトか配管かのラベルを付ける — 容赦なく。Webhook URL を切り替えるだけの設定ページは配管です。
- 環境変数を列挙し、インフラを設定するもの(認証シークレット、プロバイダーキー)とプロダクトの振る舞いを変えるものに印を付ける。
- テーブルを列挙し、キットが所有するもの(ユーザー、セッション、ワークスペース、課金状態)とあなたのものに印を付ける。
- あなたにお金をもたらすユーザージャーニーを 1 つ書き留める — そのフローが、以下のすべてのステージの受け入れテストです。
それぞれの継ぎ目をキットのモジュールに対応付ける
キットのインフラは、契約で結ばれたモジュールの集合です — あなたの仕事は、古い配管のひとつひとつを、今それを所有するモジュールに向け直すことです。ほとんどの対応付けは 1 対 1 です。そうでない場所こそ、実際に判断を下す場所です。
機械的な移植ではなく、実際に考える価値のあるマッピング選択が 2 つあります。Prisma から来るなら、キットは Drizzle に標準化しています — 移行期間中はプロダクトテーブルに Prisma を残せますが、収束する計画を立ててください。1 つのコードベースに 2 つの ORM は、複利で増える保守コストです。Prisma vs Drizzle の比較が何が変わるかを整理しています。
また、マネージド認証ベンダーから来るなら、切り替え前にベンダーの全機能を再現したくなる衝動を抑えてください。マネージド認証は、あなたのプロダクトが使っていないかもしれないもの — セッションリスト、組織 UI、Webhook 同期 — を静かに束ねています。プロダクトが実際に呼んでいるものを対応付けてください。認証の比較が重要な機能差をカバーしています。
| 現在の配管 | supastack モジュール | 引き継がれるもの |
|---|---|---|
| NextAuth.js / Auth.js セッション | 認証(Better Auth) | ユーザーとアカウントは移行。セッションは切り替え時に再発行されます |
| Clerk またはマネージド認証ベンダー | 認証(Better Auth) | ユーザーはメールで移行。サードパーティのセッションは再生されず破棄されます |
| 独自の組織/チームテーブル | ワークスペース | 組織の行はワークスペース + メンバーシップの行になります |
| 手作りの Stripe Webhook | 課金 | Stripe 顧客 ID は存続。サブスクリプション状態は再導出されます |
| CRON + setTimeout ジョブ | ジョブ(Trigger.dev) | 定期実行は共有 JobRunner 契約の背後へ移ります |
| Nodemailer / 場当たり的なメール | メール | テンプレートは移行。送信は 1 つのプロバイダーアダプター経由になります |
依存関係の順序でモジュールを採用する
マイグレーションの順序は好みではありません — 依存グラフです。以下の各ステージは個別に出荷可能で、それぞれが次のステージを始める前に検証できる、動作するシステムを生み出します。誘惑は、全部を 1 つの週末でやってしまい、その交差部分をデバッグすることです。それには耐えてください — 段階的マイグレーションは、出会うバグがすべてちょうど 1 つのモジュールのものであることを意味します。それはスキップするまで価値のわからないデバッグのスーパーパワーです。
- ステージ 1 — 骨格:キットをスキャフォールドし、環境を設定し、プロダクトルートをプロジェクト構造に落とし込む。まだ何も配線されていません。アプリは起動し、ページは描画されます。
- ステージ 2 — 認証:サインインを Better Auth へ移し、ユーザー/アカウントテーブルを移行し、サインイン + サインアウトを端から端まで検証する。以降のすべてのステージは、セッションが存在することに依存します。
- ステージ 3 — テナンシー:ワークスペースモデルを採用し、プロダクトテーブルに
workspaceIdをバックフィルし、読み取りと書き込みでスコープを強制する。ワークスペース境界の記事が真価を発揮するのはここです。 - ステージ 4 — 課金:チェックアウトと Webhook を課金モジュールに向け、既存のサブスクリプションを再マッピングし、テストモードの実際の Stripe データでエンタイトルメントチェックを検証する。
- ステージ 5 — バックグラウンド処理とメール:ジョブを JobRunner 契約へ移植し、トランザクションメールをメールアダプターへ移す。これらが最後なのは、ユーザーとワークスペースがすでに存在することに依存するからです。
データを正直に移す
データマイグレーションこそ、マイグレーションがその評判を得る場所です。退屈に保つルール:2 回実行できるスクリプトを書き、切り替えまでソースを決して変更せず、スクリプトを信用する代わりに件数を検証する。
ユーザーとアカウントは Better Auth スキーマに着地します — メールが結合キーです。パスワードベースのアカウントには判断が要ります:既存のハッシュ形式が Better Auth で検証できるものなのか、それとも切り替え時にパスワードリセットフローを出荷して強制機能として扱うのか。サードパーティの OAuth アカウントは簡単なケースです — 次回サインイン時にプロバイダーが再発行します。
課金について、正直な道は行のコピーではなく再導出です:Stripe 顧客 ID を維持し、キットの Webhook 処理にプロバイダーの真実からエンタイトルメント状態を再構築させ、計算結果を既存のサブスクリプションレコードと突き合わせます。キットのべき等な Webhook 台帳が存在するのは、まさに再生された過去のイベントが何も二重にプロビジョニングしないためです。
// migration scripts are boring by design: upsert-shaped,
// re-runnable, and they verify counts instead of trusting themselves
for (const legacyUser of legacyUsers) {
await upsertAuthUser({
email: legacyUser.email.toLowerCase(),
name: legacyUser.displayName,
externalId: legacyUser.id, // preserved for one-shot joins
});
}
const migrated = await countAuthUsers();
if (migrated !== legacyUsers.length) {
throw new Error(`count mismatch: ${migrated} of ${legacyUsers.length}`);
}あなたのもののままである部分
はっきり言っておく価値があります。ここは両方向に間違えられやすい部分だからです:あなたのプロダクトルート、スキーマ、ドメインロジック、デザインは変わりません。キットの中でアプリを作り直すのではありません — あなたのアプリをその上に載せるのです。
逆の間違いは、古い配管を新しいモジュールの隣に「一時的に」生かしておくことです。2 つ目のセッションチェック、2 つ目の Webhook ハンドラー、2 つ目のエンタイトルメントテーブル — すべての「一時的な」並行システムは、誰かが削除するまで永続的であり、そのひとつひとつが未来のインシデントです。置き換え、検証し、削除してください。あるステージがまだ古い経路を削除できないなら、そのステージは終わっていません。
切り替えチェックリスト
切り替えはイベントではなくチェックリストです。本番当日の前に、本番と同じ形のデータで、ステージング環境で少なくとも 1 回は実行してください。
- 新しい本番スナップショットに対してデータスクリプトを再実行。件数とスポットチェックが通る。
- プロダクトが対応するすべての認証プロバイダーでサインイン — 存在を忘れていたものも含めて。
- Stripe テストモードで自分のプロダクトを購入し、Webhook がエンタイトルメントまで届くのを追う。
- すべてのトランザクションメールを 1 回ずつ送信し、リンクが新しいデプロイに着地することを確認する。
- 課金されるジャーニーを端から端まで検証し、DNS/環境変数を切り替えて最初の実セッションを見守る。
- 旧デプロイを 1 週間動かし続ける — ロールバックはマイグレーションではなく DNS 変更です。
スターターキットへの移行についてよくある質問
誰かがこの作業に取りかかるたびに、同じ一握りの質問が浮かびます。
- マイグレーションには実際どれくらいかかりますか? — 標準的な認証 + Stripe の単一 Web アプリなら:週ではなく数日分の夜です。カスタム課金を持つマルチテナント B2B はステージ 3 と 4 で時間が伸びます — 伸びるのはデータ作業であり、ファイル移動では決してありません。
- 移行すべきですか、それとも新規に始めて機能を移植すべきですか? — プロダクトコードがインフラの負債より小さいなら、キットの上で新しく始めてプロダクトルートを移植する方が速いことが多いです。スキーマとドメインロジックが難しい部分なら、その場で移行してください。
- 移行期間中、既存の ORM / 認証ライブラリを残せますか? — 短期間なら、はい — 一度に 1 つのモジュール境界ずつ。永続的には、いいえ:インフラ層で何かが 2 つあることは、複利で増えるコストです。
- すでに送信されている Stripe Webhook はどうなりますか? — ステージ 4 が検証できたら、既存の Webhook エンドポイントをキットのハンドラーに向けてください。べき等な台帳が、再生・重複されたイベントを危険ではなく安全にします。
- すべてのモジュールを採用しなければなりませんか? — いいえ。モバイル、デスクトップ、ジョブ、AI は採用しても無視してもよい面です — ライセンスは基盤であり、プラットフォームごとにアプリを出荷する義務ではありません。