測驗:整合實戰 – React + FastAPI 全端專案完整 CD 流程
共 5 題,點選答案後會立即顯示結果
1. 在全端部署的 workflow 中,為什麼通常要先部署後端再部署前端?
2. 在 GitHub Actions 中,如何讓一個 job 等待另一個 job 完成後才執行?
deploy-frontend:
???: deploy-backend
runs-on: ubuntu-latest
3. 在 Vite (React) 專案中,前端要讀取環境變數必須使用什麼前綴?
4. 當前端和後端部署在不同網域時,後端需要設定什麼才能讓前端正常呼叫 API?
5. 在 workflow 中,如何將一個 job 的輸出傳遞給另一個 job 使用?
deploy-backend:
outputs:
api_url: ${{ steps.deploy.outputs.url }}
deploy-frontend:
needs: deploy-backend
env:
API_URL: ???
一句話說明
一個 workflow 同時部署前端和後端,處理好它們之間的依賴關係。
這篇教什麼
前四篇我們分別學了:
- #01 CD 的基本概念和觸發時機
- #02 使用 secrets 保護敏感資訊
- #03 部署到 Vercel
- #04 部署到 Railway
這篇把它們全部串起來,看懂一個完整的全端部署 workflow。
30 秒看懂:完整 workflow 長這樣
name: Deploy Full Stack
on:
push:
branches: [main]
jobs:
deploy-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Railway
run: railway up --service backend
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
outputs:
api_url: ${{ steps.get-url.outputs.url }}
deploy-frontend:
needs: deploy-backend # 等後端部署完
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
run: vercel deploy --prod
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VITE_API_URL: ${{ needs.deploy-backend.outputs.api_url }}
Code language: PHP (php)這段做了什麼:
- 先部署後端到 Railway
- 取得後端 API 網址
- 用這個網址部署前端到 Vercel
Monorepo vs 多 Repo:兩種專案結構
結構 1:Monorepo(前後端同一個 repo)
my-fullstack-app/
├── .github/
│ └── workflows/
│ └── deploy.yml # 一個 workflow 管兩個
├── frontend/ # React 前端
│ ├── src/
│ └── package.json
└── backend/ # FastAPI 後端
├── app/
└── requirements.txt
Code language: PHP (php)workflow 特點:
jobs:
deploy-backend:
# ...部署 backend/ 目錄
deploy-frontend:
needs: deploy-backend # 同一個 workflow 可以設定依賴
# ...部署 frontend/ 目錄
Code language: PHP (php)結構 2:多 Repo(前後端各自獨立)
# Repo 1: my-app-frontend
frontend/
├── .github/workflows/deploy.yml
└── src/
# Repo 2: my-app-backend
backend/
├── .github/workflows/deploy.yml
└── app/
Code language: PHP (php)workflow 特點:
# 各自獨立部署
# 需要手動協調順序
# 後端 API URL 要事先固定
Code language: PHP (php)翻譯:
- Monorepo:「全部放一起,一次部署,好控制順序」
- 多 Repo:「分開管理,各部署各的,要事先協調」
部署順序:為什麼後端要先部署
jobs:
deploy-backend:
runs-on: ubuntu-latest
# ...後端部署步驟
deploy-frontend:
needs: deploy-backend # 關鍵:等後端完成
runs-on: ubuntu-latest
# ...前端部署步驟
Code language: PHP (php)翻譯:needs: deploy-backend = 「這個 job 要等 deploy-backend 完成才能開始」
為什麼要這個順序?
前端需要知道 → 後端的 API 網址
後端部署完才有 → 確定的網址
所以 → 後端先部署
如果順序錯了會怎樣:
前端先部署 → 用的是舊的/錯的 API 網址 → 前端壞掉
環境變數傳遞:前端怎麼知道後端網址
步驟 1:後端部署後輸出網址
deploy-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Railway
id: deploy # 給這個步驟一個 ID
run: |
railway up --service backend
echo "url=https://my-backend.railway.app" >> $GITHUB_OUTPUT
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
outputs:
api_url: ${{ steps.deploy.outputs.url }} # 把網址傳出去
Code language: PHP (php)逐行翻譯:
id: deploy # 給這步驟取名叫 deploy
echo "..." >> $GITHUB_OUTPUT # 把值寫到 GitHub 的輸出
outputs: # 這個 job 要對外輸出的值
api_url: ${{ steps.deploy.outputs.url }} # 從 deploy 步驟拿 url
Code language: PHP (php)步驟 2:前端使用這個網址
deploy-frontend:
needs: deploy-backend # 需要先等後端
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Frontend
run: npm run build
env:
VITE_API_URL: ${{ needs.deploy-backend.outputs.api_url }}
# ↑ 從 deploy-backend job 拿 api_url
- name: Deploy to Vercel
run: vercel deploy --prod
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
Code language: PHP (php)翻譯:${{ needs.deploy-backend.outputs.api_url }} = 「從我依賴的 deploy-backend job 取得它輸出的 api_url」
React 前端怎麼用這個環境變數
// frontend/src/api.js
const API_URL = import.meta.env.VITE_API_URL; // Vite 用 VITE_ 前綴
async function fetchUsers() {
const response = await fetch(`${API_URL}/users`);
// ↑ 這會變成 "https://my-backend.railway.app/users"
return response.json();
}
Code language: JavaScript (javascript)CORS 設定:讓前端可以呼叫後端
問題:為什麼需要 CORS?
前端網址:https://my-app.vercel.app
後端網址:https://my-backend.railway.app
瀏覽器:「這兩個網域不同,不能直接呼叫!」
Code language: JavaScript (javascript)後端 FastAPI 的 CORS 設定
# backend/app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import os
app = FastAPI()
# 允許的前端網址
allowed_origins = [
"https://my-app.vercel.app", # 正式環境
"http://localhost:5173", # 本地開發
]
# 也可以從環境變數讀取
if os.getenv("FRONTEND_URL"):
allowed_origins.append(os.getenv("FRONTEND_URL"))
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins, # 允許哪些網址來呼叫
allow_credentials=True,
allow_methods=["*"], # 允許所有 HTTP 方法
allow_headers=["*"], # 允許所有 headers
)
@app.get("/users")
async def get_users():
return [{"name": "Alice"}, {"name": "Bob"}]
Code language: PHP (php)逐行翻譯:
allow_origins=allowed_origins # 只有這些網址可以呼叫我
allow_credentials=True # 允許帶 cookie
allow_methods=["*"] # GET, POST, PUT, DELETE 都可以
allow_headers=["*"] # 所有 header 都可以
Code language: PHP (php)在 workflow 中設定 FRONTEND_URL
deploy-backend:
steps:
- name: Deploy to Railway
run: railway up --service backend
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
FRONTEND_URL: "https://my-app.vercel.app" # 告訴後端前端在哪
Code language: PHP (php)完整 Workflow 範例
name: Deploy Full Stack App
on:
push:
branches: [main]
env:
FRONTEND_URL: https://my-app.vercel.app
BACKEND_URL: https://my-backend.railway.app
jobs:
# ============ 後端部署 ============
deploy-backend:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Railway CLI
run: npm install -g @railway/cli
- name: Deploy Backend
id: deploy-backend
working-directory: ./backend
run: |
railway up --service backend
echo "url=${{ env.BACKEND_URL }}" >> $GITHUB_OUTPUT
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
FRONTEND_URL: ${{ env.FRONTEND_URL }}
- name: Health Check
run: |
sleep 30 # 等待部署生效
curl --fail ${{ env.BACKEND_URL }}/health || exit 1
# ↑ 如果 health check 失敗,整個 workflow 失敗
outputs:
api_url: ${{ steps.deploy-backend.outputs.url }}
# ============ 前端部署 ============
deploy-frontend:
needs: deploy-backend # 等後端完成
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install Dependencies
working-directory: ./frontend
run: npm ci
- name: Build
working-directory: ./frontend
run: npm run build
env:
VITE_API_URL: ${{ needs.deploy-backend.outputs.api_url }}
- name: Deploy to Vercel
working-directory: ./frontend
run: |
npm install -g vercel
vercel deploy --prod --token ${{ secrets.VERCEL_TOKEN }}
# ============ 部署後驗證 ============
verify-deployment:
needs: [deploy-backend, deploy-frontend] # 等兩邊都完成
runs-on: ubuntu-latest
steps:
- name: Verify Backend Health
run: |
response=$(curl -s ${{ env.BACKEND_URL }}/health)
echo "Backend response: $response"
- name: Verify Frontend
run: |
response=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.FRONTEND_URL }})
if [ "$response" != "200" ]; then
echo "Frontend returned $response"
exit 1
fi
echo "Frontend is healthy"
- name: Test API Connection
run: |
# 模擬前端呼叫後端
curl -s ${{ env.BACKEND_URL }}/users \
-H "Origin: ${{ env.FRONTEND_URL }}" \
|| echo "API test completed"
Code language: PHP (php)這個 workflow 的流程
push to main
│
▼
┌─────────────────┐
│ deploy-backend │ 先部署後端
└────────┬────────┘
│ 輸出 api_url
▼
┌─────────────────┐
│ deploy-frontend │ 再部署前端(使用後端網址)
└────────┬────────┘
│
▼
┌─────────────────────┐
│ verify-deployment │ 最後驗證兩邊都正常
└─────────────────────┘
常見問題排除
問題 1:CORS 錯誤
Access to fetch at 'https://backend.railway.app/users' from origin
'https://frontend.vercel.app' has been blocked by CORS policy
Code language: JavaScript (javascript)檢查點:
# 後端的 allowed_origins 有沒有包含前端網址?
allowed_origins = [
"https://frontend.vercel.app", # ← 確認有這個
]
Code language: PHP (php)問題 2:環境變數沒生效
// 前端顯示 undefined
console.log(import.meta.env.VITE_API_URL); // undefined
Code language: JavaScript (javascript)檢查點:
# 1. 環境變數名稱要用 VITE_ 開頭(Vite 專案)
env:
VITE_API_URL: ... # 對
API_URL: ... # 錯,前端讀不到
# 2. 要在 build 時設定,不是 deploy 時
- name: Build
run: npm run build
env:
VITE_API_URL: ${{ needs.deploy-backend.outputs.api_url }} # 對
- name: Deploy
run: vercel deploy
env:
VITE_API_URL: ... # 太晚了,build 已經結束
Code language: PHP (php)問題 3:部署順序問題
Error: needs.deploy-backend.outputs.api_url is empty
Code language: CSS (css)檢查點:
# 後端 job 有沒有正確設定 outputs?
deploy-backend:
outputs:
api_url: ${{ steps.deploy-backend.outputs.url }}
# ↑ 這個 id 要和 step 的 id 一致
steps:
- name: Deploy
id: deploy-backend # ← 確認有這個 id
run: |
echo "url=https://..." >> $GITHUB_OUTPUT
Code language: PHP (php)問題 4:Health check 失敗
curl: (7) Failed to connect to my-backend.railway.app port 443
Code language: CSS (css)檢查點:
# 部署後需要等待一段時間
- name: Health Check
run: |
sleep 30 # 等 30 秒讓部署生效
curl --fail ${{ env.BACKEND_URL }}/health
# 或用重試機制
- name: Health Check with Retry
run: |
for i in {1..5}; do
curl --fail ${{ env.BACKEND_URL }}/health && exit 0
echo "Attempt $i failed, waiting..."
sleep 10
done
exit 1
Code language: PHP (php)最佳實踐整理
1. 固定網址 vs 動態網址
# 方式 1:固定網址(簡單,推薦初學者)
env:
BACKEND_URL: https://my-backend.railway.app
FRONTEND_URL: https://my-app.vercel.app
# 方式 2:動態取得(進階,更靈活)
- name: Get Railway URL
id: get-url
run: |
url=$(railway status --json | jq -r '.deployments[0].url')
echo "url=$url" >> $GITHUB_OUTPUT
Code language: PHP (php)2. 平行部署 vs 順序部署
# 如果前端不需要後端的動態資訊,可以平行部署
jobs:
deploy-backend:
# ...
deploy-frontend:
# 沒有 needs,兩個同時跑
verify:
needs: [deploy-backend, deploy-frontend] # 等兩邊都完成再驗證
Code language: PHP (php)3. 回滾策略
- name: Deploy with Rollback
run: |
# 記錄當前版本
current_version=$(railway status --json | jq -r '.version')
# 嘗試部署
railway up || {
echo "Deploy failed, rolling back..."
railway rollback $current_version
exit 1
}
Code language: PHP (php)Vibe Coder 檢查點
看到全端部署的 workflow 時確認:
- [ ] 部署順序對嗎?後端需要先部署嗎?
- [ ] 環境變數有正確傳遞嗎?前端拿得到後端網址嗎?
- [ ] CORS 設定正確嗎?後端允許前端的網域嗎?
- [ ] 有健康檢查嗎?部署後有驗證服務正常嗎?
- [ ] secrets 都設好了嗎?RAILWAYTOKEN、VERCELTOKEN 都有嗎?
系列總結
經過這 5 篇文章,你現在能看懂:
| 篇數 | 主題 | 你學會看懂的 |
|---|---|---|
| #01 | CD 基礎 | on: push 觸發條件 |
| #02 | Secrets | ${{ secrets.XXX }} 敏感資訊 |
| #03 | Vercel | 前端部署 workflow |
| #04 | Railway | 後端部署 workflow |
| #05 | 整合 | 完整全端部署流程 |
當你看到 AI 生成的部署 workflow,你現在知道:
- 每一行在做什麼
- 為什麼要這樣寫
- 出問題時怎麼排查
這就是 Vibe Coder 需要的:不是自己寫,而是看懂 AI 寫的,然後判斷對不對。
延伸:知道就好
這些進階功能遇到再查:
- Matrix 部署:同時部署到多個環境(staging、production)
- GitHub Environments:設定不同環境的 secrets 和保護規則
- Deployment Status API:在 PR 中顯示部署預覽連結
- Reusable Workflows:把部署流程抽成可重用的 workflow
進階測驗:整合實戰 – React + FastAPI 全端專案完整 CD 流程
測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。
共 5 題,包含情境題與錯誤診斷題。
1. 你正在設計全端專案的 CI/CD 流程,前端使用 React (Vite),後端使用 FastAPI。團隊想要讓前後端可以獨立部署,但部署時又能自動取得最新的後端網址。以下哪種架構設計最合適? 情境題
2. 前端部署完成後,使用者回報 API 呼叫失敗。你在瀏覽器 console 看到以下錯誤訊息: 錯誤診斷
Access to fetch at ‘https://my-backend.railway.app/users’ from origin
‘https://my-app.vercel.app’ has been blocked by CORS policy:
No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
3. 你的 workflow 中前端 build 完成,但 import.meta.env.VITE_API_URL 在執行時顯示 undefined。檢查 workflow 設定如下: 錯誤診斷
deploy-frontend:
needs: deploy-backend
steps:
– name: Build
run: npm run build
– name: Deploy to Vercel
run: vercel deploy –prod
env:
VITE_API_URL: ${{ needs.deploy-backend.outputs.api_url }}
4. 你的團隊希望在前後端都部署完成後,自動執行健康檢查驗證服務正常。應該如何設計 workflow? 情境題
5. 後端部署到 Railway 後,你執行健康檢查但持續失敗。查看 workflow log 顯示: 錯誤診斷
curl: (7) Failed to connect to my-backend.railway.app port 443: Connection refused
你的健康檢查步驟如下:
– name: Health Check
run: curl –fail ${{ env.BACKEND_URL }}/health