---
title: "Web App Architecture：production web app 在雲端上有哪些部分"
description: "從 DNS、CDN、runtime、DB 到 queue 與 observability，拆解 production web app 的基本架構，以及 solo developer 該如何選擇 managed service。"
lang: "zh-Hant-TW"
tags: ["web-architecture", "cloud", "backend", "DevOps"]
pubDate: 2026-06-05T00:00:00.000Z
updatedDate: 2026-07-23T00:00:00.000Z
---

前端專案在 localhost 正常執行，還不等於使用者已經能在網路上使用它。要把 web app 交到使用者手上，接下來會遇到一連串問題：網址要怎麼指向網站？HTML、CSS 和 JavaScript 要放在哪裡？需要在伺服器執行的程式要跑在哪裡？資料要存去哪裡？上線後發生錯誤，又要怎麼知道？

**Web app architecture 談的就是：系統需要哪些部分、各自在解決什麼問題，以及資料如何在它們之間流動。** 一個只有靜態內容的網站，可能只需要 hosting 和 CDN；如果需要登入、資料寫入、檔案上傳或背景工作，就可能會需要 auth、runtime、database、object storage 或 queue。

這篇關心的不是如何讓大型社群網站支撐十億名使用者，也不是 system design 面試裡的極端流量題，而是一個人如何把 web app 完整上線。對 solo founder 或接案工程師來說，團隊裡通常沒有另外的 SRE 或 DevOps 可以接手，因此除了理解每個元件的責任，也要知道哪些工作可以交給雲端業者的代管服務。

## 為什麼值得學

- **AI agent 降低了一個人完成產品的門檻。** 工程師也更常需要同時處理架構設計、前後端程式與部署，不再有明確的職務分界。
- **代管服務讓小團隊有能力維護完整系統。** 現在已經有各種雲端服務，把許多原本需要專門人力的工作包成可直接使用的產品。工程師不必自己搭完所有基礎設施，但仍要知道服務替自己處理了什麼。
- **LLM agent 可以產生程式碼，卻不能替你承擔架構決策。** agent 已經能協助分析需求、建立程式碼與執行任務，但使用哪些服務、如何取捨成本與可靠度，仍需要人決定。

## 定位與脈絡

### 一句話：什麼是 web app architecture

Web app architecture 描述的是一組分工清楚、彼此連接的元件。理解架構時，可以依序問四個問題：系統有哪些元件、每個元件解決什麼問題、資料如何在它們之間流動，以及各元件要採用代管服務，還是自行建置與維運。前三個問題不太受產品更替影響；最後一個則會隨著服務內容與價格持續改變。因此，這篇著重在相對穩定的架構概念。

### 一個請求的完整旅程

使用者在瀏覽器輸入網址後，請求通常會沿著以下路徑取得回應：

```mermaid
flowchart LR
  U[User browser] -->|DNS lookup| D[DNS]
  D --> CDN[CDN / edge cache]
  CDN -->|cache hit: static asset| U
  CDN -->|cache miss / dynamic| LB[Load balancer<br/>+ TLS + WAF]
  LB --> R[Application runtime<br/>server / serverless / edge]
  R --> DB[(Managed DB)]
  R --> OBJ[(Object storage)]
  U -->|register / sign in| AUTH[Auth]
  AUTH -->|session / token| U
  R -->|verify identity| AUTH
  R --> EXT[External APIs<br/>LLM / email / payments]
  R --> Q[Queue]
  Q --> W[Background worker]
  W --> DB
  W --> EXT
  R -.-> OBS[Logs / metrics / traces<br/>alerts]
  W -.-> OBS
  R -.-> SEC[Secrets store]
  W -.-> SEC
```

圖上的方塊代表系統角色，不對應特定服務商。**初學時不需要先認識每個角色的所有產品，更實際的做法是先選一套平台，把一個小型 web app 從部署、登入、讀寫資料一路接到檔案上傳。** 等到遇到成本、功能或法規限制，再替換其中一個服務即可。只要先記得：每個角色通常都有其他替代服務，不是選定平台後就永遠不能更換。

可以先從以下四套選一套：

- <a class="architecture-platform-tag architecture-platform-tag--firebase" href="https://firebase.google.com/products">Firebase</a>：把 hosting、runtime、database、storage 和 auth 集中在同一個開發平台，適合先理解前端如何使用 backend service。
- <a class="architecture-platform-tag architecture-platform-tag--cloudflare" href="https://developers.cloudflare.com/">Cloudflare</a>：以 CDN、edge runtime 和 storage 為核心，適合 web app，也有許多可在 free tier 內練習的服務。
- <a class="architecture-platform-tag architecture-platform-tag--vercel" href="https://vercel.com/docs">Vercel</a>：從前端 framework 的部署體驗出發，hosting、function 與 CDN 整合度高；database 和 auth 通常再透過 Marketplace 接入。
- <a class="architecture-platform-tag architecture-platform-tag--amplify" href="https://docs.amplify.aws/react/">AWS Amplify</a>：AWS 提供給 web 與 mobile app 的整合式開發平台，將 hosting、auth、data、storage 與 function 包成比直接挑選 AWS 服務更容易入門的入口。

下表的產品名稱是「角色對照表」，不是購物清單。同一列只要選一個能解決需求的服務，不需要全部使用。架構圖只畫 DNS，因為 request 過程只會經過 DNS 解析；表格則把購買網域與設定 DNS records 合在同一列，對應實際上線時要完成的工作。

| 角色                           | 負責的工作                                                                                                                          | 先從四套平台找什麼                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Domain / DNS**               | 購買網域、設定 DNS records，將 domain 指向 hosting                                                                                  | Namecheap；<span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> Registrar / DNS；<span class="architecture-platform-tag architecture-platform-tag--vercel">Vercel</span> Domains / DNS；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> Route 53。<span class="architecture-platform-tag architecture-platform-tag--firebase">Firebase</span> 需連接外部的 registrar 與 DNS                                                           |
| **CDN**                        | 將靜態資源或可快取的 HTML 放在靠近使用者的節點，也能協助抵擋 DDoS                                                                   | <span class="architecture-platform-tag architecture-platform-tag--firebase">Firebase</span> Hosting / App Hosting；<span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> CDN；<span class="architecture-platform-tag architecture-platform-tag--vercel">Vercel</span> CDN；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> Amplify Hosting                                                                                             |
| **Load balancer / 反向代理**   | 把流量分配給多個 runtime instance，並處理 TLS、健康檢查與 WAF                                                                       | 這四套代管平台通常已經包在 hosting 或 runtime 前面，第一個專案不用另外挑產品                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| **Application runtime**        | 執行應用程式，包括 server、serverless function 與 edge function                                                                     | <span class="architecture-platform-tag architecture-platform-tag--firebase">Firebase</span> App Hosting / Cloud Functions；<span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> Workers；<span class="architecture-platform-tag architecture-platform-tag--vercel">Vercel</span> Functions；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> Amplify Functions。另見 [JS runtime](/articles/frontend-to-fullstack/javascript-runtimes) |
| **Database**                   | 長期儲存與查詢應用程式資料                                                                                                          | <span class="architecture-platform-tag architecture-platform-tag--firebase">Firebase</span> Firestore / Data Connect；<span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> D1；<span class="architecture-platform-tag architecture-platform-tag--vercel">Vercel</span> Marketplace database integration；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> Amplify Data                                                                 |
| **KV store（選用）**           | 儲存需要低延遲 key-value 讀寫的資料，例如 session 或 rate limit counter；不是泛指各層的 cache                                       | <span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> KV；其他平台的第一個專案通常先使用 database，真的遇到獨立 KV 的需求再加                                                                                                                                                                                                                                                                                                                                                |
| **Object storage**             | 儲存使用者上傳、靜態檔案、封存 log 與備份                                                                                           | <span class="architecture-platform-tag architecture-platform-tag--firebase">Firebase</span> Cloud Storage；<span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> R2；<span class="architecture-platform-tag architecture-platform-tag--vercel">Vercel</span> Blob；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> Amplify Storage（Amazon S3）                                                                                        |
| **Queue（選用）**              | 讓 request 先回應，不讓耗時工作持續占用處理 request 的 runtime；運算仍由 background worker 執行，queue 負責排隊、重試與吸收流量尖峰 | <span class="architecture-platform-tag architecture-platform-tag--firebase">Firebase</span> task queue functions；<span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> Queues；<span class="architecture-platform-tag architecture-platform-tag--vercel">Vercel</span> Queues；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> Amazon SQS。沒有背景工作時先不要加                                                                     |
| **Secrets store**              | 保管 API key、資料庫密碼與憑證，避免寫入 Git                                                                                        | <span class="architecture-platform-tag architecture-platform-tag--firebase">Firebase</span> secrets；<span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> Workers secrets；<span class="architecture-platform-tag architecture-platform-tag--vercel">Vercel</span> sensitive environment variables；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> Amplify secrets                                                                   |
| **Observability**              | 收集 log、metric 與 trace，並在異常時發出 alert                                                                                     | 先用各平台內建 log；需要跨前後端追蹤 error 時，再接 Sentry                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| **Auth**                       | 確認使用者身分，並處理註冊、登入與 session；資料存取權限仍須由 backend 或 access rules 強制執行                                     | <span class="architecture-platform-tag architecture-platform-tag--firebase">Firebase</span> Authentication；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> Amplify Auth。<span class="architecture-platform-tag architecture-platform-tag--cloudflare">Cloudflare</span> 和 <span class="architecture-platform-tag architecture-platform-tag--vercel">Vercel</span> 可接外部 auth service                                                                                       |
| **Email / transactional 通訊** | 寄送帳號驗證、密碼重設、收據與系統通知                                                                                              | 這通常是平台外的專門服務，例如 Resend；<span class="architecture-platform-tag architecture-platform-tag--amplify">AWS</span> stack 也可使用 Amazon SES                                                                                                                                                                                                                                                                                                                                                                 |

> [!TIP]
> **練習時，目標可以是讓最小架構完整落在 free tier，而不是把表中的角色全部加進去。** 先使用平台提供的免費子網域；只有真的需要長任務時才加 queue，需要上傳檔案時才加 object storage。自有網域通常仍要向 Namecheap 之類的 registrar 支付年費。各家的「免費」條件也不同：Firebase 有些服務需要啟用 Blaze billing 才能部署，但仍提供 no-cost quota；Vercel Hobby 限個人、非商業用途；AWS Free Tier 則有帳號資格、credit 與期限限制。正式上線前，仍要請 AI 幫你重新查看各家服務的最新價格條件。

如果想用一篇文章補齊整體概念，可以讀 Jonathan Fulton 的 [Web Architecture 101](https://medium.com/storyblocks-engineering/web-architecture-101-a3224e126947)。文章以 Storyblocks 當時的 production stack 為例，逐段說明請求如何從 DNS 抵達資料庫。它寫於 2017 年，因此沒有涵蓋後來普及的 serverless 與 edge runtime，但基礎架構仍然適用，而且內容不綁定單一雲端服務商。

如果想看一套平台如何把這些角色組合起來，可以讀 [Cloudflare 的 full-stack application 架構圖](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/fullstack-application/) 或 [AWS Amplify 文件](https://docs.amplify.aws/react/)。這類文件會把每個角色對應到自家產品，但不代表每個角色都只能使用該公司的服務。閱讀時先辨認角色的責任與資料流，不用急著記住所有產品名稱。

### Managed vs self-host：solo dev 的預設

對 solo developer 而言，沒有特殊限制時，通常先選代管服務比較實際。

- 很多服務不是一開始設定好就結束。以資料庫為例，磁碟容量、備份、版本升級、replication 和慢查詢都要持續處理，這些工作會直接占用開發產品的時間。對仍在驗證市場需求、流量也不高的產品來說，代管服務的費用通常低於自行維運的人力成本。
- Vendor lock-in 確實存在，但程度要看服務是否採用標準協定。採用標準協定的主流服務通常可以遷移，只是過程仍會產生工程成本和 downtime。產品還在早期時，不必先為所有假想的遷移情境增加複雜度。

自行維運通常要有明確理由，例如法規或資料主權要求、代管費用已經高到值得配置維運人力，或應用程式的查詢模式需要非常細緻的調校。如果尚未遇到這些限制，基本上都沒有必要先承擔額外的維運工作。

### 程式的相容設計

架構不只決定基礎設施怎麼安排，應用程式本身也要採用相容的設計，否則更換 runtime instance 或外部服務時仍然會卡住。[The Twelve-Factor App](https://12factor.net/) 整理了十二項適合雲端部署的應用程式原則。對 solo developer 來說，以下幾項最常直接影響部署與維運：

- **[Config in env](https://12factor.net/config)**：secret、資料庫 URL 與 API key 應由環境注入，不要寫死在程式碼裡，更不能提交到 Git。更多安全方面的說明，可以參考另一篇[介紹 secret 管理的文章](/articles/security/secret-management)。
- **[Stateless processes](https://12factor.net/processes)**：Process 不保存狀態，runtime instance 才能隨時增加、替換或重新啟動；需要長期保存的資料則放進 object storage 或資料庫。
- **[Backing services as attached resources](https://12factor.net/backing-services)**：把資料庫等 backing service 當作可替換的外部資源，並透過設定連接，替換服務時就不需要改寫應用程式邏輯。
- **[Dev/prod parity](https://12factor.net/dev-prod-parity)**：開發與 production 環境如果設計成差異太大，問題就要等到上線才會發現。

### 從能執行到能長期維護

Demo 只要能完成主要流程即可；production 系統還要降低故障機率、及早發現異常、控制成本，並在資料遺失或服務中斷時恢復運作。以下幾件事不一定難，卻很容易在趕著上線時被忽略：

- **Secret 管理**
- **系統的 observability 與 alert**
- **實際做過還原演練**
- **限制濫用與異常流量**
- **獨立的 staging 環境**

## 實際練習

這個練習只做一個最小留言板，共分成五步。第一步先把後面四步的完整需求交給 AI，比較 Firebase、Cloudflare、Vercel、AWS Amplify，選出最適合初學者、而且能免費完成全部練習的主平台與必要外接服務；接著才依序部署靜態頁面、加入註冊登入、建立純文字留言，最後讓留言可以附圖片。每次只增加一項使用者需求，讓架構因為實際問題逐步長出來；不需要為了用到前文提過的所有角色，另外加入 queue、external API 或 email。

這是用來建立架構概念的入門練習，不是 production 選型，也不是完整的 production-ready 檢查；secret、alert、還原演練、流量限制與 staging 仍要依上一節逐項處理。練習先使用平台提供的免費子網域，不必購買網域。涉及平台功能、free tier、付款方式與超額計費時，請 agent 當下查閱官方文件，不要憑記憶回答。

五步可以各自開一個新的 session，但不能只靠對話紀錄傳遞狀態。第一步先在專案建立 `docs/web-app-architecture-lab.md`，之後每一步都在開始時閱讀、結束前更新。這份交接檔至少要包含：

- 五步的固定需求與範圍。
- 選定的主平台、外接服務、選擇理由與官方文件來源。
- 目前完成到哪一步，以及實際使用的專案檔案與設定名稱。
- 預計的角色與服務對照，以及每一步完成後的 Mermaid 架構圖、角色責任與變更原因。
- 已執行的驗證、結果、未解問題與下一步。

交接檔只能記錄環境變數或 secret 的名稱，不能寫入 credential 值。更新時要保留前一步的架構圖與決策，若決策已經失效則標記原因，不要直接覆蓋。下一個 agent 也不能把交接檔當成絕對正確的事實：開始工作前仍要對照實際程式碼、部署設定與平台狀態，發現內容過期時先修正交接檔。

後面四次實作迭代都依照同一個順序進行：

1. **理解需求**：先說明目前版本為什麼無法滿足新需求。
2. **修改架構**：由自己先畫新版資料流，標出新增的角色與責任，再請 agent 訂正。
3. **實際串接**：只加入這次需要的服務，保持前一版功能繼續運作。
4. **驗證結果**：測試正常與失敗路徑，從平台介面或 log 找到證據，最後更新架構圖。

### 第 1 步：選擇最適合初學者的免費平台

這一步不是替 production app 選型，而是替初學者選一套容易看懂、容易操作的教學環境。先把五步的固定範圍交給 AI：同一個留言板需要 static hosting、managed auth、application runtime、managed DB 與 object storage，但不需要 queue、external API、email 或自有網域。必要條件是五步都能在免費額度內完成，而且不依賴限時試用 credit；接著才比較需要外接多少服務、文件是否清楚、設定步驟、錯誤訊息與各服務的管理介面是否適合學習。

把以下 prompt 複製到新的 session：

```
我要用一個最小留言板，分五步學習 web app architecture：
1. 選擇最適合初學者、能免費完成練習的主平台與必要外接服務。
2. 部署只有假留言的靜態頁面。
3. 加入使用者註冊、登入、保持登入狀態與登出。
4. 讓登入者建立純文字留言，由 runtime 驗證 session 後寫入 managed DB。
5. 讓登入者建立附一張圖片的留言，圖片放 object storage，DB 只存 metadata。

這個練習不需要 queue、external API、email、自有網域或完整的 production hardening。請先
閱讀目前專案，再依下列方式協助我選擇：

1. 用目前的官方文件比較 Firebase、Cloudflare、Vercel、AWS Amplify。逐一確認 static
   hosting、auth、runtime、DB 與 object storage 能否在免費額度內完成；主平台缺少角色時，
   外接服務也必須免費。不能把限時試用 credit 當成免費方案。
2. 先排除必須實際付費才能完成五步的方案。若需要綁付款方式、開啟 billing，或用量超過後
   會自動收費，必須明確標示風險；初學練習優先選不用綁付款方式，或能設定硬性用量上限的方案。
3. 對剩下的方案比較初學者的學習體驗：需要建立幾個帳號、要外接多少服務、官方文件是否
   有完整入門路徑、local development 與部署步驟、錯誤訊息，以及管理介面能否直接看見
   auth user、DB record、runtime log 與 storage object。不要以 production scalability、
   商業功能或 vendor lock-in 當主要評分標準。
4. 推薦一套最容易學習的免費主平台與必要外接服務，但先等我確認，不要建立專案或部署。
   如果四套都無法符合條件，不要硬選；說明卡在哪個角色，再提出一套同樣適合初學者且能
   免費完成五步的替代組合。
5. 我確認後，在專案建立 docs/web-app-architecture-lab.md，記錄固定需求、角色與服務對照、
   免費方案的官方文件證據與查閱日期、選擇理由、目前步驟和下一步。這一步不要先畫最終
   架構圖；Mermaid 圖要等後續每新增一項需求時再畫。只能記錄環境變數或 secret 名稱，
   不能寫入任何 credential 值。

如果官方文件與你的既有知識不同，以目前官方文件為準；無法確認的地方要明確標示。
```

### 第 2 步：部署靜態留言板

先建立只有前端的留言板，畫面放幾筆寫死在程式碼裡的假留言。這一版還不能註冊，也不能真的建立留言；重點是把靜態檔案部署到雲端，觀察使用者輸入網址後，瀏覽器如何取得 HTML、CSS 與 JavaScript。

把以下 prompt 複製到新的 session：

```
繼續 docs/web-app-architecture-lab.md 裡規劃的最小留言板。這是第 2 步：把只有前端的
靜態留言板部署到雲端。請擔任我的教練，不要一開始就替我畫好架構：

1. 先閱讀現有程式碼、部署設定與 docs/web-app-architecture-lab.md，確認交接內容和專案
   實際狀態一致。沿用已確認的主平台與外接服務，不要重新選型。
2. 讓我先畫出 Browser、DNS、CDN / hosting 之間的 request path，再幫我訂正。特別指出
   使用平台免費子網域時，DNS、TLS 與 CDN 哪些由平台代為處理。
3. 建立只有前端的留言板，放幾筆寫死的假留言，部署到平台免費子網域。不要加入 auth、
   runtime、database、object storage 或自有網域。
4. 部署後跟我一起驗證公開網址、HTTPS 與靜態檔案是否正常，再問我每個架構角色解決什麼
   問題，以及哪些維運責任已經交給平台。

完成後更新交接檔的目前步驟、實際檔案、部署網址、驗證結果、未解問題與下一步，並新增
這個練習的第一張架構圖。平台資訊只採用官方文件；交接檔不得寫入 credential 值。
```

### 第 3 步：讓使用者註冊與登入

沿用同一個留言板與平台，加入 managed auth。登入後只要顯示目前的使用者與登出按鈕，這一版仍然不能建立留言。把範圍停在身分與 session，可以看清楚 auth service 儲存的是使用者身分，不是應用程式的留言資料。

把以下 prompt 複製到新的 session：

```
繼續 docs/web-app-architecture-lab.md 裡的留言板與既有 stack。這是第 3 步：加入註冊、
登入與登出，但還不要建立留言或加入 application database。請擔任我的教練：

1. 先閱讀交接檔、現有程式碼與部署設定，核對第 2 步真的完成，再讓我說明現有靜態架構
   為什麼不知道使用者是誰，並畫出 Browser、hosting、auth service 與 session / token
   之間的資料流，再幫我訂正。
2. 依平台目前的官方文件接上 managed auth，完成註冊、登入、保持登入狀態與登出。不要
   自行設計密碼儲存或自製 auth。
3. 登入後顯示目前的使用者，登出後回到未登入狀態；不要在這輪加入留言寫入、DB 或圖片。
4. 跟我一起驗證註冊、登入、重新整理與登出，並從 auth service 的管理介面確認使用者存在。
   最後問我 auth service、session 與 application data 各自負責什麼。

完成後更新交接檔的目前步驟、實際檔案、設定名稱、驗證結果、未解問題與下一步，並新增
本步完成後的架構圖，不要覆蓋前一步版本。平台行為、安全設定與限制只採用官方文件；
交接檔不得寫入 credential 值。
```

### 第 4 步：建立純文字留言

登入之後，下一項需求是讓使用者建立純文字留言。這一輪加入 application runtime 與 managed DB：runtime 驗證 session、處理留言規則，再把資料寫入 DB。為了清楚練習前後端的權限邊界，前端不能自行指定留言作者；runtime 必須從驗證過的 session 取得 user ID。

把以下 prompt 複製到新的 session：

```
繼續 docs/web-app-architecture-lab.md 裡的留言板與既有 stack。這是第 4 步：讓已登入的
使用者建立純文字留言，並在重新整理或重新部署後保留資料。請擔任我的教練：

1. 先閱讀交接檔、現有程式碼與部署設定，核對第 3 步真的完成，再讓我說明 auth 為什麼
   不能代替 application database，並畫出 Browser、runtime、auth 與 DB 之間的資料流，
   再幫我訂正。
2. 加入平台的 application runtime 與 managed DB。留言至少保存文字、作者 ID 與由
   backend 產生的建立時間；不要加入圖片、object storage 或其他服務。
3. Backend 必須驗證 session，並從驗證結果取得作者 ID，不能相信前端傳來的 userId。
   前端隱藏按鈕只能改善使用體驗，不能當成權限控制。未登入者直接呼叫寫入端點時，backend
   必須拒絕請求。
4. 跟我一起驗證：登入者可以留言、未登入者會被拒絕、假造 userId 不能冒用別人、DB
   看得到正確資料，而且重新部署 runtime 後留言仍然存在。最後讓我更新架構圖並說明為什麼
   runtime 應保持 stateless。

完成後更新交接檔的目前步驟、實際檔案、設定名稱、驗證結果、未解問題與下一步，並新增
本步完成後的架構圖，不要覆蓋前一步版本。平台 API、session 驗證與 DB 用法只採用官方
文件；交接檔不得寫入 credential 值。
```

### 第 5 步：建立附圖片的留言

最後讓留言可以附一張圖片，並加入 object storage。圖片本體放進 object storage，DB 只保存留言文字、作者 ID、object key 或 URL 等結構化資料。這次要特別觀察：一次建立留言會跨越 DB 與 object storage，任一邊失敗時都可能留下不完整資料。

把以下 prompt 複製到新的 session：

```
繼續 docs/web-app-architecture-lab.md 裡的留言板與既有 stack。這是第 5 步：讓已登入的
使用者建立一則附圖片的留言。請擔任我的教練：

1. 先閱讀交接檔、現有程式碼與部署設定，核對第 4 步真的完成，再讓我說明為什麼不應直接
   把圖片本體塞進 application DB，並畫出 Browser、runtime、auth、DB 與 object storage
   之間的資料流，再幫我訂正。
2. 依平台目前官方建議的上傳方式加入 object storage。圖片本體放 storage；DB 只保存
   留言、作者與 object key / URL 等必要 metadata。先限制為每則一張圖片，不加入縮圖、
   圖片處理、queue 或 external API。
3. 上傳與讀取權限必須由 backend 或 storage access rules 強制執行，不能只靠前端隱藏
   上傳按鈕。限制允許的檔案類型與大小，也不要把 storage credential 放進前端程式碼。
4. 跟我一起驗證：正常留言、未登入上傳、超過大小限制、檔案上傳失敗等路徑。確認失敗時
   不會留下指向不存在圖片的留言，並討論刪除留言時是否也要刪除圖片。最後更新完整架構圖。

完成後更新交接檔的目前步驟、實際檔案、設定名稱、驗證結果與未解問題，新增本步完成後的
架構圖，不要覆蓋前一步版本，並將下一步改成整體回顧。平台的 storage、權限與上傳限制只
採用官方文件；交接檔不得寫入 credential 值。
```

完成五步後，回頭比較交接檔裡的架構變化：平台選擇先確認整條路徑可行；靜態頁面只需要 hosting；使用者身分帶來 auth 與 session；長期保存留言需要 runtime 與 DB；檔案上傳才需要 object storage。這個練習的目的不是把所有雲端服務都串過一次，而是能說明每個角色是因為哪一項需求才加入，以及它替系統承擔了什麼責任。能完成主要流程只代表 demo 可以運作；正式提供給使用者前，還要回到上一節檢查觀測、還原、流量限制與環境隔離。

## 延伸主題

這篇先交代整體架構，沒有深入每項服務的實作細節。接下來要研究資料庫、queue、observability 或部署流程，可以由目前專案最常遇到的問題決定；不需要照固定順序把所有主題學完。
