前言

上一篇 把同一個 Service Center 掛到 Order Index、Order Status 與 Profile。這個案例的特殊之處是沒有獨立 web server:Customer Account block、Admin App Home 與 Admin App Tools 都是 extension bundles。

「沒有後端」很容易被誤解成「不能讀資料」,也容易反過來被誇大成「Shopify 會幫忙代管所有資料與商業流程」。真正的邊界要看資料在哪裡、surface 是 customer 還是 admin、需不需要 secret、長時間工作與外部系統。

原本的做法與問題症狀

第一個方向是為每個功能先架一台 server、database 與 authentication callback。若需求只是顯示 target context、連到內建訂單頁或讀 Shopify 已提供的資料,這會增加:

  • hosting 與 secret management。
  • session token validation。
  • CORS、timeout、retry 與 observability。
  • database retention 與 privacy inventory。

另一個極端是「既然 extension 能直接 query Shopify,就永遠不需要 backend」。只要需求包含第三方保固系統、客服工單、私有計算、secret、queue、scheduled job 或 cross-shop data,這個結論就不成立。

案例還有一個容易混淆的地方:Admin App Home 能用 direct Admin API 查 app-owned metaobjects,不代表 Customer Account block 也自動擁有 Admin API。兩個 surface 的 runtime、identity 與 API contract 不同。

問題是怎麼推敲出來的?

Root package 只有 extension workspaces 與 Shopify CLI scripts,沒有 web framework、server runtime 或 database client。App configuration 使用 Shopify-hosted App Home,Customer Account extension 則由 Shopify 代管 compiled bundle。

Source data flow 分成兩條:

customer surface
  → Customer Account block
  → target context + links
  → api_access = false
  → network_access disabled

admin surface
  → App Home
  → embedded app direct Admin API
  → app-owned metaobjects
  → legacy lookup / FAQ management

Admin lookup code會查 customer summary 與 legacy order metaobjects;但 Customer Account Service Center 沒有 import 這段 model,也沒有 query 或 fetch。這證明「資料模型已存在」和「顧客端已接線」是兩件事。

最後採用的架構

先用能力梯子判斷是否需要 backend:

需求 優先能力 是否需要自家 backend
顯示 target 提供的 order/account context Target APIs
查 profile、orders、addresses Customer Account API 通常否
查 products、collections、公開 storefront data Storefront API+api_access 通常否
讀預先寫入的 customer/order metafield Customer Account API/metafield API 通常否
管理員操作 app-owned data Admin extension direct API 視 surface 與操作而定
呼叫第三方保固、ERP、CRM network_access+session token
保存 secret、跑 queue/cron、跨商店彙整 App backend

Extension-only 不是架構目標,而是最小責任的結果。只要 Shopify 平台資料與 extension runtime 已能安全完成需求,就不額外引入 backend;一旦越過能力邊界,就明確新增 server,而不是把 secret 或 privileged logic 塞進 bundle。

關鍵實作

Customer Account API 可直接由 extension 發 request,平台處理 authentication:

const response = await fetch(
  'shopify://customer-account/api/2026-07/graphql.json',
  {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      query: `query {
        customer {
          id
          firstName
        }
      }`,
    }),
  },
);

是否能讀特定欄位仍受 protected customer data access 約束。

若是 storefront products/collections,啟用:

[extensions.capabilities]
api_access = true

再用 shopify.query()。這個 capability 不是 Admin API scope,也不是任意 external fetch。

只有要呼叫自家 server 或第三方服務時才開:

[extensions.capabilities]
network_access = true

Server 端要驗證 extension 取得的 session token,並處理 sandboxed Web Worker 的 CORS。不能把 API secret、Admin access token 或 third-party credential 編進 extension JavaScript。

實際驗證

2026-07-30 的 source inspection 證明:

  • App 沒有獨立 server runtime dependency。
  • Customer Account Service Center 沒有 API query 或 external fetch。
  • api_access 關閉,network_access 沒有啟用。
  • Admin App Home 使用 direct Admin API 查 app-owned metaobjects。
  • Legacy lookup 位於 admin surface,不是 customer surface。
  • 三個既有 compiled bundles 分別約 23 KB、26 KB 與 2 KB。

官方 64 KB 限制是每個 compiled UI extension bundle;不能把三個 bundle 相加後當成同一個 deploy limit。

官方文件查核也確認:Customer Account API direct access 不需額外 capability;Storefront API 需要 api_access;external services 需要 network_access,而 network capability 要先在 Partner Dashboard 申請發布存取。

本次沒有從 clean install 重跑 source project build,沒有 deploy 新 App Version,也沒有用測試 customer 驗證 API fields。既有 bundles 只能當 artifact evidence,不是當日 production proof。

仍然存在的限制

第一,source 的 API versions 不一致:Customer Account block、Admin App Home、Admin tools 與 app config 使用不同季度版本。它們可能都仍在支援窗口,但 release checklist 應統一記錄每個 surface 的 version、breaking change 與 upgrade owner。

第二,沒有 backend 就沒有自家的 durable job、retry queue、server log 與 secret store。若操作需要長時間執行或可追溯重試,不能依賴顧客開著頁面直到完成。

第三,metafield/metaobject 在 Shopify 裡不等於自動適合 customer exposure。要區分 app-owned、merchant-owned、Storefront visibility、Customer Account API 可讀範圍與 protected data。

第四,Admin App Home 的 direct API 執行在管理員權限脈絡;Customer Account extension 執行在登入顧客脈絡。共享 TypeScript model 不會自動共享 authorization。

第五,開啟 network access 後,不只多一個 fetch()。還要有 session token validation、CORS、rate limit、timeout、retry、data minimization、privacy deletion 與 incident response。

最後,Shopify 代管 extension code,不代管外部服務 SLA。售後、保固或 legacy system 只要有一段離開 Shopify,就需要明確 owner 與 fallback。

可以延伸到哪些情境?

可以用一句判斷:

平台已提供、短時間、無 secret、單一登入脈絡
  → 先做 extension-only

外部資料、privileged logic、長時間工作、跨脈絡
  → 加 backend

這比「所有 App 都要有 server」或「UI Extension 完全不需要 server」更實際。架構不是按技術偏好選,而是按資料信任邊界與失敗復原需求選。

下一篇把這條界線放進遷移計畫:從舊會員中心搬到 Shopify New Customer Accounts,功能如何分階段遷移?

參考資料

以上平台文件查核日期:2026-07-30。