用 Lovable 做 App 的第一件事不是寫功能,是設好這三個環境
Lovable 讓你在 30 分鐘內做出一個能跑的 app。然後問題就來了:你叫它加一個功能,它加了,直接推到 production。使用者正在用的東西,就這樣被改了,沒有測試、沒有確認、沒有退路。這不是 Lovable 的問題,是沒有分環境的問題。這篇把我從零搭起 dev / staging / production 三層環境的完整路徑走一遍,最後附上 Lovable 包過的 TanStack Start 上 Vercel 真正跑通的部署配方。
為什麼第一件事是設環境
大部分人剛開始用 Lovable,工作流長這樣:
Lovable 改 code → 推 GitHub main → 直接上 production
一個人、app 剛起步的時候,這樣可以接受。但只要有真實使用者,問題就開始浮出來:
- 你加了新功能,還沒測完,使用者就看到壞掉的畫面
- 你改了資料庫 schema,migration 跑壞,production 資料受影響
- 你想退版本,但 Lovable 推的 commit 一大串,不知道要 revert 到哪一個
分三層環境,解的就是這件事:
| 環境 | 用途 | 壞掉的代價 |
|---|---|---|
| dev | Lovable 日常開發、測新功能 | 零,這裡本來就是拿來玩的 |
| staging | 上線前最後一道驗收 | 低,只有你跟測試帳號在用 |
| production | 真實使用者 | 高,所以東西進來之前要先驗過 |
關鍵在那個「驗過」。dev 隨便壞,staging 攔住沒驗的東西,production 只接收已經驗證的版本。少了中間這層,每次改動都是直接賭在使用者身上。
架構長什麼樣子
GitHub repo
├── dev branch ← Lovable 指向這裡,日常 AI 開發
│ └── Supabase taskflow-dev project
├── staging branch ← dev 穩定後 PR 過來測試
│ └── Supabase taskflow-staging project
└── main branch ← staging 驗收通過後 PR 過來
└── Supabase taskflow-production project
每個 branch 對應一個獨立的 Supabase project,各自有自己的資料庫、RLS policy、API key,環境之間完全隔離。
Lovable 在這套架構裡只負責一件事:平時指向 dev branch,絕大多數時間都在那裡工作。要上 staging 或 production,一律走 GitHub PR,不讓 Lovable 直接改。這條規矩是整套環境隔離的前提。
把環境搭起來
三個 Supabase project
到 Supabase dashboard 建三個 project:taskflow-dev、taskflow-staging、taskflow-production。每個都記下三項資訊:
- Project ID (= ref):Settings → General → Project ID,一串英數,像
omvmggaxajioxuhbizkx - Project URL:就是
https://<Project ID>.supabase.co,新版 dashboard 不另外列,自己組 - Publishable key:Settings → API Keys → Publishable key,值是
sb_publishable_...,可以放前端
DB password 是建 project 時自己設的那組。
這裡有個新版改版的坑要先講:Supabase 的 API Keys 頁改過了。舊的 anon / service_role (那串 eyJ... 的 JWT) 被移到「Legacy anon, service_role API keys」分頁;預設顯示的是新制的 Publishable / Secret key (sb_publishable_ / sb_secret_)。前端要用的是 Publishable key。
還有一個配額限制要先有心理準備:Supabase 免費方案同時只能有 2 個 active project,三環境需要 3 個。建好第三個之後,把暫時不用的 (通常是 dev) pause 掉,要用再 resume,或者直接升 Pro。後面踩雷那段會看到,連到 paused 的 project 會噴出看起來像 CORS 的錯,其實只是 project 沒醒。
GitHub branch 與 Secrets
從 main 各拉一個 staging 跟 dev branch,然後在 Lovable 右上角的 branch picker 把 active branch 切到 dev。從這一刻起,Lovable 所有 AI 改動都推到 dev,不會碰到 main。
接著到 GitHub repo 的 Settings → Secrets and variables → Actions,加 6 個 secret,給待會的自動部署用:
SUPABASE_ACCESS_TOKEN # Supabase 帳號的 Personal Access Token
# 取得:Supabase Dashboard → Account → Access Tokens
DEV_PROJECT_ID # taskflow-dev 的 project ref
DEV_DB_PASSWORD # taskflow-dev 的 DB 密碼
STAGING_PROJECT_ID # taskflow-staging 的 project ref
STAGING_DB_PASSWORD # taskflow-staging 的 DB 密碼
PRODUCTION_PROJECT_ID # taskflow-production 的 project ref
PRODUCTION_DB_PASSWORD # taskflow-production 的 DB 密碼
讓 migration 自己跑
三個環境各配一個 GitHub Actions workflow,邏輯都一樣:對應 branch 一有 push,就把 migration 推到對應的 Supabase project。放在 .github/workflows/ 底下。
deploy-dev.yml:
name: Deploy Migrations to Dev
on:
push:
branches: [dev]
jobs:
deploy:
runs-on: ubuntu-latest
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.DEV_DB_PASSWORD }}
SUPABASE_PROJECT_ID: ${{ secrets.DEV_PROJECT_ID }}
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
- run: supabase link --project-ref $SUPABASE_PROJECT_ID
- run: supabase db push
deploy-staging.yml 跟 deploy-production.yml 是同一份,只要把 branches 換成 [staging] / [main],env 三個值換成對應環境的 secret 就好。三個檔案放好,每條 branch 的 schema 改動就會自己同步到它該去的資料庫,不用手動 db push。
Vercel per-branch 環境變數
到 Vercel 匯入這個 repo,部署設定走預設。重點在環境變數:每個變數要設三份,各自綁不同 branch。
| 變數名稱 | dev | staging | main (production) |
|---|---|---|---|
VITE_SUPABASE_URL | taskflow-dev URL | taskflow-staging URL | taskflow-production URL |
VITE_SUPABASE_PUBLISHABLE_KEY | dev key | staging key | production key |
設定方式:新增變數時在「Environment」選 Preview 並指定特定 branch,不要全選。main 那組則綁 Production。這樣每個 branch 部署出來,連的就是自己那個 Supabase project。
Branch protection
最後到 GitHub 的 Settings → Branches,給 main 跟 staging 各加一條 ruleset:要求 merge 前先開 PR、不允許繞過。個人 repo 的 approval 數可以設 0,但 PR 這道關卡要留著。dev 不設保護,因為 Lovable 需要直接 push。
到這裡,環境骨架就完整了。平時在 Lovable (active: dev) 開發,dev 穩了開 PR 到 staging 驗收,staging 過了再 PR 到 main 上 production。每一步都有 GitHub Actions 同步 migration,每個環境連自己的資料庫。
真正的硬仗:Lovable → Vercel 部署配方
上面那套是「應該長怎樣」。真正花掉我最多時間的,是讓 Lovable 做出來的東西能在 Vercel 上正常跑起來。這段是整篇最有價值的部分。
問題的根源是一個沒人先告訴你的事實:Lovable 新版產出的是 TanStack Start (SSR) + Nitro 專案,不是純前端 SPA。 所以「把 repo 直接丟 Vercel」會一路撞牆。官方文件說「Vercel 會自動偵測 TanStack Start」,那是指純 TanStack Start;Lovable 包過的版本,自動偵測整個失效,每一層都得手動覆蓋。網路上的教學幾乎都是純版,套不進 Lovable。
下面是實際跑通的配方,四件事缺一不可。
1. 強制 Nitro 輸出 Vercel 格式
Lovable 的 @lovable.dev/vite-tanstack-config 把 Nitro 的 target 寫死成 Cloudflare,輸出目錄寫死成 dist/*。要在 vite.config.ts 全部蓋回來:
import { defineConfig } from "@lovable.dev/vite-tanstack-config";
export default defineConfig({
tanstackStart: {
server: { entry: "server" },
},
nitro: {
preset: "vercel",
output: {
dir: ".vercel/output",
serverDir: ".vercel/output/functions/__server.func",
publicDir: ".vercel/output/static",
},
},
});
2. 憑證走 Vercel per-branch 變數,不要留 committed .env
Lovable 預設會 commit 一份 .env,指向它自己的 Lovable Cloud DB,這份會干擾你的設定。把它 .gitignore 掉並刪除,憑證全部交給上面設好的 Vercel per-branch 變數。
理由是環境隔離:committed .env 每條 branch 一份,merge 的時候會互相覆蓋,把隔離打破。憑證不進 git,這是 12-factor 的基本原則。
3. 每個 branch 都要逼 client.ts 重編一次
這個坑最隱晦。Vite 的 build cache 是用檔案內容當 key,不追蹤環境變數。也就是說,你改了 Vercel 變數、重新部署,照樣會拿到沿用舊 env 的快取 bundle,因為檔案內容沒變、hash 沒變。
解法是去 src/integrations/supabase/client.ts 加或改一行註解,逼 Vite 重編這個讀 env 的模組,env 才會真的被 bake 進去。每條 branch 都要各做一次。
4. 記得 Supabase 的 2-active 上限
前面提過的配額限制,在這裡會以很迷惑的形式出現。三環境要 3 個 project,免費同時只能開 2 個,所以你得 pause / resume 輪流。連到 paused 的 project 時,瀏覽器會噴 CORS 失敗 / status null。那不是真的 CORS 問題,是目標 project 還在睡。resume 它就好。
踩雷對照表
把錯誤訊息對到真正原因,省下你重複我踩過的路:
| 症狀 | 真正原因 | 解法 |
|---|---|---|
| 部署 Ready 但打開 404 | Nitro 用 Cloudflare 預設輸出 wrangler.json;或輸出在 dist/client 而 Vercel 找 dist | preset: "vercel" 加上 output 指回 .vercel/output |
| 沒有 Functions 區塊 | preset 沒真的套到 vercel,輸出沒進 .vercel/output | 上面那組 output 三件設定 |
| 瀏覽器端 Missing PUBLISHABLE_KEY | Vite 快取沿用舊 env;或 committed .env 蓋台 | 逼 client.ts 重編,並移除 .env |
| CORS 失敗 / status null | 目標 Supabase project 被 pause | resume 該 project (注意 2-active 上限) |
跑一次完整流程驗收
骨架搭好、部署配方套好之後,拿一個小功能把整條鏈路驗一遍。我加的是「待辦清單的截止日期欄位」:
- dev:在 Lovable (active: dev) 加欄位 → 確認
devbranch 有新 commit → 確認deploy-devworkflow 成功 → 確認 taskflow-dev 的 table 出現新欄位 - staging:開 PR
dev → staging並 merge → 確認deploy-staging成功 → 打開 Vercel 的 staging URL 看到新功能 - production:開 PR
staging → main並 merge → 確認deploy-production成功 → 打開 production URL 確認正常
三條都綠,代表 code、migration、環境變數三者在每一層都對得起來。之後的日常開發,就是不斷重複這條 dev → staging → main 的路徑,每一步都有 PR 把關,每個環境連自己的資料庫。
收尾
設環境這件事,急著做功能的時候最容易跳過。Lovable 給你的即時感太強,30 分鐘有個能動的東西,很難忍住不直接推上線。但「能動」跟「能安全地一直改」是兩回事,中間差的就是這套 dev / staging / production。
整套搭下來,最花時間的不是概念,是 Lovable 包過 TanStack Start 之後那些被改寫的預設值:Nitro target、輸出目錄、Vite 快取行為。這些在純 TanStack Start 的教學裡都不會出現,只能自己一層層撞出來。把這份配方留在這,希望接下來想走一樣路的人,可以少撞幾次。
如果你也在用 Lovable 做產品、想把它接成一條能安心上線的流程,歡迎找我聊聊。