前言
Shopify App repository 常把 Web 後台、Function、Theme App Extension、Prisma schema 與 shopify.app.toml 放在一起。它們在同一個 Git commit 裡,卻不代表會由同一個平台、同一個命令、在同一時間發布。
這次把 Web host 搬到 Vercel 時,最容易出現的誤會就是:「Vercel production 已經 Ready,所以 Shopify App 已完成部署。」實際上 Web 只完成四個部署面中的一個。
上一篇記錄了 Shopify App 搬到 Vercel 與 Neon 的遷移策略。這篇把同一次 release 拆成 Web、Shopify App Version、Database 與 Store Config,建立可以驗收與回滾的共同語言。
原本的做法與問題症狀
原本發布常被縮成一條線:
git push
→ build 成功
→ deployment Ready
→ Shopify App 上線
這條線對只有單一 Web service 的網站勉強成立,對 Shopify App 卻少了三層狀態。
例如 Web backend 已經支援新版 Function 設定,但舊 App Version 仍在商店執行;或 Prisma code 已讀取新欄位,production database migration 尚未執行。另一種情況是 Function 與 Web 都完成部署,但商店的 Theme App Embed 沒有啟用,商家仍看不到 storefront 顯示。
這些症狀都不是「快取還沒更新」,而是每個部署面根本有自己的 authority。
問題是怎麼推敲出來的?
Shopify 官方目前明確說明:shopify app deploy 會建立一個 App Version,內容是 app configuration 與所有 extensions 的 snapshot,並把它 release 給使用者;這個命令不會部署 Web App,Web 仍要交給自己的 hosting solution。
因此同一個 repository 至少有兩條 release:
- Hosting provider 部署 React Router Web。
- Shopify CLI 建立並 release App Version。
資料庫又是第三條。Prisma migration 會改 schema,dump/restore 會搬資料,兩者都不會因為 Vercel deployment Ready 或 Shopify App Version release 自動完成。
最後是每間 Store 的狀態。App 是否安裝、scopes 是否授權、Theme App Embed 是否啟用、商家是否已建立 Promotion、Automatic Discount 是否存在,都是 store-level state,不能只從 Git 或 deployment dashboard 判斷。
最後採用的架構
四個部署面可以這樣理解:
| 部署面 | Authority | 主要內容 | 成功證據 |
|---|---|---|---|
| Web | Hosting provider | SSR、loader/action、OAuth、Webhook、App Proxy | deployment healthy、HTTP smoke test |
| App Version | Shopify | App config、Functions、Extensions | released app version |
| Database | PostgreSQL/migration system | schema、Session、Campaign、Snapshot、Lease | migration status、資料檢查 |
| Store Config | 個別商店 | 安裝、授權、embed、Promotion 與操作狀態 | dev store/production smoke test |
四面之間有依賴,但不是一個 transaction:
Database compatible
↓
Web deployment healthy
↓
App Version released
↓
Store verification
實際順序要依變更調整。例如先加入 backward-compatible database 欄位,再部署會讀新舊欄位的 Web,最後 release 會產生新格式的 Function,通常比一次做破壞性切換安全。
關鍵實作
每次 release 記錄四個版本,而不是只記一個 Git SHA:
type ShopifyRelease = {
webCommit: string;
appVersion: string;
dbMigration: string;
storeVerification: 'pending' | 'passed' | 'failed';
};
Web pipeline 應負責:
- Build React Router server bundle。
- 注入 hosting secrets。
- 部署並確認 health endpoint。
- 驗證 OAuth、Webhook 與 App Proxy 可到達。
Shopify release pipeline 則負責選對 production config,再執行:
shopify app deploy --config production
這會建立 App Version,但不會把 React Router server 上傳到 Shopify。
Database pipeline 只處理 migration 與資料檢查。它不能被埋在每個 Function cold start,也不應因為 Web rollback 就自動 rollback data。
Store verification 最後檢查實際商店:embedded app 能否開啟、Session 是否可用、Theme App Embed 是否啟用、Function 是否讀到相容設定、Webhook 與 App Proxy 是否打到正確 Web host。
實際驗證
來源案例的 Git history 提供了三種可交叉檢查的證據:
- Vercel adapter、build command 與 entry server 在一個遷移 commit 中完成。
- Staging Shopify config 先更新到穩定 host。
- Production config 的 App URL 與 callback 在後續獨立 commit 切換。
這證明 Web 適配與 Shopify configuration cutover 並不是同一個動作。來源 runbook 也把 database migration、Web deployment、Shopify config deploy 與 store smoke test 列成不同完成條件。
官方文件則驗證 shopify app deploy 只發布 configuration 與 extensions,不包含 Web App;舊 App Version 可以重新 release,Web deployment 的回滾則由 hosting provider 處理。
仍然存在的限制
這個四面模型是 release mental model,不是自動化保證。若 pipeline 沒有把版本彼此關聯,仍可能出現「Web 是 commit A、Function 是 version B、DB 是 migration C」卻無法追查的狀況。
App Version rollback 也不等於完整 rollback。舊 Function 可能不理解新 metafield;舊 Web 可能不理解新 schema;database 已接受的新寫入更不能直接忽略。
Store Config 最難完全自動化。Theme App Embed、商家操作與既有資源常需要 dev store fixture、受控 production smoke test 或人工確認。
此外,Shopify 的 App Version release 可能需要幾分鐘才套用到已安裝商店;不能在 CLI 回傳成功的同一瞬間就假設所有 Store 都已完成升級。
可以延伸到哪些情境?
四面模型可以延伸成 release checklist:
- 這次改動觸碰哪幾個面?
- 每個面的 authority 是誰?
- 相容窗口如何維持?
- 每個面如何驗證與回滾?
- 哪些 state 已經接受新寫入,不能倒退?
只改 Web UI 的 release 可能只碰一面;修改 Discount Function config schema 則可能同時碰 Web、App Version、Database 與 Store Config。先標出面向,能讓「部署成功」變成可驗證的具體敘述。
下一篇會處理四面中最容易超過單一 request 邊界的工作:Serverless 環境如何處理 Shopify 大量商品更新?
參考資料
- Shopify:Deploy app versions
- Shopify CLI:app deploy
- Shopify:Manage app config files
- Shopify:Deploy to a hosting service
以上平台文件查核日期:2026-07-25。
