> ## Documentation Index
> Fetch the complete documentation index at: https://supastack.dev/llms.txt
> Use this file to discover all available pages before exploring further.
> For the full documentation corpus in one file, fetch: https://supastack.dev/llms-full.txt

# 把現有 SaaS 遷入 supastack

一份分階段的操作手冊：把正在運轉的 SaaS —— 認證、租戶、計費、任務 —— 遷移到 supastack 地基上，不重寫，也不需要停機一個週末。

_釋出於 2026-10-29._
_更新於 2026-10-29._

> **如何把現有的 SaaS 遷移到 supastack？**
> 這取決於你的程式碼庫，但遷移順序不變：分階段遷移，絕不一氣重寫。把倉庫盤點為產品程式碼（路由、模式、領域邏輯——仍是你的）和基礎設施（認證、租戶、計費、任務、郵件——由套件替換），按依賴順序接入模組：結構、認證、工作區、計費，然後是後臺任務。用可重跑的腳本遷移資料，保留 Stripe 客戶 ID，驗收通過後再切換。大多數 Web 應用只需要幾個晚上，而不是幾週。

## 動程式碼之前先盤點

每一次搞砸的遷移都以同樣的方式開始：有人打開套件倉庫就開始刪檔案。套件是地基，不是主題——它替換的是你的基礎設施，而對你的產品沒有任何意見。在動任何東西之前，誠實地把倉庫分成兩堆。

產品程式碼是客戶能向你複述的一切：你的路由、模式、領域邏輯、文案、定價頁。基礎設施是他們永遠看不到的一切：會話、租戶檢查、支付 Webhook、排隊任務、事務郵件。第一堆原封不動地遷走。第二堆被替換——這正是全部意義所在。

- 列出每個路由組，標註是產品還是管道——別手軟；只用來切換 Webhook URL 的設定頁也是管道。
- 列出你的環境變數，標出哪些配置基礎設施（認證金鑰、供應商金鑰）、哪些改變產品行為。
- 列出你的表，標出哪些將由套件擁有（使用者、會話、工作區、計費狀態）、哪些是你的。
- 寫下那條為你帶來收入的使用者旅程——這條流程就是下面每個階段的驗收測試。


## 把每條接縫映射到套件模組

套件的基礎設施是一組以契約相連的模組——你的工作是把手邊每一段舊管道指向現在擁有它的模組。大多數映射是一一對應的；對不上的地方，才是你真正要做決策的地方。

有兩處映射選擇值得真正思考，而不是機械照搬。如果你來自 `Prisma`，套件標準化在 Drizzle 上——過渡期內產品表可以繼續用 Prisma，但要計畫收斂；一個程式碼庫裡兩個 ORM 是會利滾利的維護稅。[Prisma vs Drizzle 對比](https://supastack.dev/zh-tw/blog/prisma-vs-drizzle-for-saas.md)講清了會發生什麼變化。

如果你來自託管認證廠商，忍住那種想在切換前把廠商功能全部複刻一遍的衝動。託管認證悄悄捆綁了你的產品可能不用的東西——會話列表、組織 UI、Webhook 同步。只映射你的產品實際呼叫的東西；[認證對比](https://supastack.dev/zh-tw/blog/better-auth-vs-nextauth-vs-clerk.md)覆蓋了那些真正要緊的能力差異。

| 你現在的管道 | supastack 模組 | 遷移過來的東西 |
|---|---|---|
| NextAuth.js / Auth.js 會話 | 認證（Better Auth） | 使用者與賬戶遷移；會話在切換時重新簽發 |
| Clerk 或託管認證廠商 | 認證（Better Auth） | 使用者按郵箱遷移；第三方會話被放棄，而不是重放 |
| 自建組織/團隊表 | 工作區 | 你的組織行變成工作區 + 成員關係行 |
| 手搓的 Stripe Webhook | 計費 | Stripe 客戶 ID 保留；訂閱狀態重新推導 |
| CRON + setTimeout 任務 | 任務（Trigger.dev） | 定時工作移到共享的 JobRunner 契約之後 |
| Nodemailer / 臨時郵件 | 郵件 | 模板遷走；發送走同一個供應商介面卡 |


## 按依賴順序採用模組

遷移順序不是偏好問題——它是依賴圖。下面的每個階段都可以單獨釋出，並且每個階段都產出一個在下一階段開始前可驗證的可工作系統。誘惑是在一個週末做完整個事情、然後除錯它們的交集；忍住——分階段遷移意味著你遇到的每個 bug 都恰好只屬於一個模組，這是一種除錯超能力，跳過它之前你不會意識到它的價值。

- 階段 1——骨架：搭起套件腳手架，配置環境變數，把產品路由放進專案結構。還沒有任何接線；應用能啟動，頁面能渲染。
- 階段 2——認證：把登入遷到 Better Auth 上，遷移使用者/賬戶表，端到端驗證登入 + 登出。之後的每個階段都依賴會話的存在。
- 階段 3——租戶：採用工作區模型，在產品表上回填 `workspaceId`，在讀寫中強制執行範圍限定。[工作區邊界](https://supastack.dev/zh-tw/blog/workspace-boundaries.md)一文正是在這裡發揮作用。
- 階段 4——計費：把結帳和 Webhook 指向計費模組，重新映射現有訂閱，用測試模式下的真實 Stripe 資料驗證權益檢查。
- 階段 5——後臺任務與郵件：把任務移植到 JobRunner 契約上，把事務郵件遷到郵件介面卡。它們放在最後，因為它們依賴使用者和工作區已經存在。


## 誠實地遷移資料

資料遷移正是遷移名聲的來源。讓它保持無聊的規則：寫一個可以跑兩遍的腳本；切換前絕不動源資料；驗證筆數而不是相信腳本。

使用者和賬戶落在 Better Auth 模式裡——郵箱是連接鍵，基於密碼的賬戶需要做個決定：要麼你現有的雜湊格式是 Better Auth 能驗證的，要麼在切換時上線一個重設密碼流程，把它當作強制手段。第三方 OAuth 賬戶是簡單的情形——供應商會在下次登入時重新簽發。

對計費來說，誠實的路徑是重新推導而不是複製行：保留 Stripe 客戶 ID，讓套件的 Webhook 處理用供應商的真實狀態重建權益，再把它算出的結果與你現有的訂閱記錄做對比。套件的[冪等 Webhook 賬本](https://supastack.dev/zh-tw/blog/idempotent-billing-webhooks.md)正是為此而存在的——被重放的歷史事件不會重複供應任何東西。

```
// 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}`);
}
```


## 屬於你的部分不變

這點值得直說，因為人們兩個方向都會搞錯：你的產品路由、模式、領域邏輯和設計都不變。你不是在套件裡面重建應用——而是把你的應用安放在它上面。

反方向的錯誤是讓舊管道在新模組旁邊“暫時”活著。第二個會話檢查、第二個 Webhook 處理器、第二張權益表——每個“臨時”的並行系統在有人刪除它之前都是永久的，而每一個都是未來的事故。替換、驗證、刪除。如果某個階段還不能刪掉舊路徑，它就還沒完成。


## 切換檢查清單

切換是一份檢查清單，不是一個事件。在真正的日期之前，先在預發布環境裡用接近生產的資料至少完整跑一遍。

- 用一份新的生產快照重跑資料腳本；筆數與抽查全部透過。
- 用產品支援的每個認證供應商登入一遍——包括那些你忘了它們存在的。
- 在 Stripe 測試模式下購買自己的產品，跟著 Webhook 一路追到權益生效。
- 每封事務郵件都發一次；檢查連結落在新的部署上。
- 端到端驗證付費旅程，然後切 DNS/環境變數，看著第一個真實會話進來。
- 讓舊部署再跑一週——回滾是一次 DNS 修改，不是一次遷移。


## 關於遷入腳手架的常見問題

每當有人坐下來做這件事，出現的總是同一批問題。

- 遷移實際要花多久？ —— 標準認證 + Stripe 的單個 Web 應用：是幾個晚上，不是幾周。帶自定義計費的多租戶 B2B 會在第三、四階段加時間——拉長的是資料工作，從來不是檔案搬運。
- 我該遷移，還是從頭開始再移植功能？ —— 如果你的產品程式碼比基礎設施債更小，在套件上從頭開始、移植產品路由通常更快。如果模式和領域邏輯才是難點，就原地遷移。
- 過渡期能保留現有 ORM / 認證庫嗎？ —— 短暫保留，可以——一次一個模組邊界。永久保留，不行：基礎設施層裡任何東西有兩份都是利滾利的稅。
- 已經在路上的 Stripe Webhook 怎麼辦？ —— 第四階段驗證透過後，把現有 Webhook 端點指向套件的處理器；冪等賬本讓重放或重複的事件變得安全而不是危險。
- 必須採用每個模組嗎？ —— 不必。移動端、桌面端、任務和 AI 是可以採用也可以無視的端面——許可證給你的是地基，不是每個平臺都必須發一個應用的命令。
