「防災平台不只是程式碼的集合,更是一套能穩定運行的生命線。」
在前面的篇章裡,我們談過願景、使用者研究、清單架構、AI 模組與 UX 優化。
今天要跨入一個對所有Side Project都至關重要的議題:
如何把專案從「我的電腦」帶到「所有人的日常」。
這就是部署(Deployment),以及更進一步的持續整合與持續交付(CI/CD)。
防災資訊的價值,在於「即時」與「可用」。
對「韌性生活指南」專案而言,部署不只是技術手段,而是 將防災知識轉化為全民可用工具的最後一哩路 。
為什麼選擇這個組合?
對一般使用者來說,他們不會在意 CI/CD 的細節,但會直接感受到:
1.手機/電腦同步: 不論在哪個裝置打開,都是最新版本。
2.快速回應: 清單生成與電商串接,不因伺服器延遲而卡頓。
3.穩定可靠: 颱風來襲、同時多人使用,也不會崩潰。
這正呼應了 「韌性生活指南」的願景:穩定而堅韌的日常 。
1.短期:
2.中期:
3.長期:
部署不是專案的「最後一步」,而是 專案能不能真正被使用的起點 。
從 GitHub 的程式碼,到 Vercel 的自動化部署,再到 MongoDB Atlas 的安全儲存,每一環都在回答一個問題:
「在災難發生時,這個平台能不能即時、可靠地幫助到使用者?」
這就是 CI/CD 在「韌性生活指南」專案中的價值:
讓技術不只是存在於工程師的世界,而是成為每個家庭的守護者。
1.建立專案與套件
npx create-next-app@latest resilient-kit --ts --eslint
cd resilient-kit
npm i mongodb mongoose zod @tanstack/react-query
npm i -D vitest @testing-library/react playwright @playwright/test
2.建立基本目錄
/app # Next.js App Router
/app/api/** # API route handlers (Server Actions / Route handlers)
/lib/db.ts # 連線池與DB工具
/models/** # Mongoose schemas
/tests/** # 單元/端對端測試
/scripts/** # seeding、維運腳本
DoD: 專案可在本機 npm run dev
啟動,首頁可見。
1.建立專案與叢集
2.建立資料庫使用者(最小權限)
app_user
僅賦予讀寫應用資料庫(例:resilient_kit
)。3.IP 存取
0.0.0.0/0
但不建議)。4.連線字串
MONGODB_URI
,格式:mongodb+srv://app_user:<PASSWORD>@cluster0.xxxxx.mongodb.net/resilient_kit?retryWrites=true&w=majority
5.資料模型(範例)
// /models/Item.ts
import { Schema, model, models } from "mongoose";
const ItemSchema = new Schema({
name: { type: String, required: true },
category: { type: String, enum: ["必備","情境","選配"], required: true },
expiresInDays: Number,
tags: [String]
}, { timestamps: true });
export default models.Item ?? model("Item", ItemSchema);
DoD: 可由本機成功連線 Atlas,CRUD 正常;DB 使用者權限最小化。
1.連線工具
// /lib/db.ts
import mongoose from "mongoose";
const MONGODB_URI = process.env.MONGODB_URI!;
if (!MONGODB_URI) throw new Error("Missing MONGODB_URI");
let isConnected = 0 as 0|1;
export async function connectDB() {
if (isConnected) return;
await mongoose.connect(MONGODB_URI, { dbName: "resilient_kit" });
isConnected = 1;
}
2.健康檢查API
// /app/api/health/route.ts
import { NextResponse } from "next/server";
import mongoose from "mongoose";
export async function GET() {
const state = mongoose.connection.readyState; // 1 = connected
return NextResponse.json({ ok: state === 1, state });
}
DoD: 打 /api/health
回傳 { ok: true }
。
1.建立GitHub Repo ,推送本機程式碼。
2.分支保護
main
:受保護,需 PR 並通過測試。dev
:開發整合分支。1.Import GitHub Repo 到Vercel。
2.建立環境
main
3.環境變數(在 Vercel Project → Settings → Environment Variables)
MONGODB_URI
(Prod/Preview 各自設定)OPENAI_API_KEY
(如用 AI 模組)NEXT_PUBLIC_BASE_URL
(可選)4.Build 指令
next build
.vercel/output
(自動)1.加入 Vitest(單元測試)
// package.json
"scripts": {
"test": "vitest run",
"test:ui": "vitest"
}
// /tests/unit/recommender.test.ts
import { describe, it, expect } from "vitest";
import { recommend } from "@/lib/recommender";
describe("recommender", () => {
it("returns essentials by default", () => {
const res = recommend({ people: 1 });
expect(res.essentials.length).toBeGreaterThan(0);
});
});
2.加入 Playwright(E2E)
npx playwright install
// /tests/e2e/basic.spec.ts
import { test, expect } from "@playwright/test";
test("能生成清單", async ({ page }) => {
await page.goto(process.env.PREVIEW_URL ?? "http://localhost:3000");
await page.getByLabel("家中人數").selectOption("3");
await page.getByRole("button", { name: "產生清單" }).click();
await expect(page.getByText("你的專屬清單")).toBeVisible();
});
3.GitHub Actions(可選)
若你要在 GitHub 跑測試(再交由 Vercel 部署):
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run test
- run: npx playwright install --with-deps
- run: npx playwright test
DoD: PR會自動執行單元+E2E 測試;測試不過不可合併。
1.環境變數只放在 Vercel ,不進 Git。
2.API金鑰保護: 伺服器端 Route Handlers(不要在 client 暴露機密)。
3.CORS/Rate Limit (可選):防止濫用 API。
4.Mongo 資安
1.Atlas建立兩個DB: resilient_kit_dev
/ resilient_kit_prod
。
2.**Vercel設兩組 ** MONGODB_URI
(Preview 用 dev、Production 用 prod)。
3.資料種子腳本(選用)
# /scripts/seed.ts
# 以 ts-node 執行,注入必備/情境/選配範例
DoD: Preview 永遠不會污染 Production 資料;測試人員可在預覽站自由玩。
Vercel 無常駐 Cron,可用 Vercel Cron(Beta)或外部 Scheduler(如 GitHub Actions、Railway)。
1.建立提醒 API
// /app/api/cron/expire-check/route.ts
import { NextResponse } from "next/server";
import { connectDB } from "@/lib/db";
import Item from "@/models/Item";
export async function GET() {
await connectDB();
// 查即將到期的物資,發通知(Email/LINE)
// ...
return NextResponse.json({ ok: true });
}
2.設定排程 (Vercel → Settings → Cron Jobs)
0 3 * * *
觸發 GET https://your.app/api/cron/expire-check
1.指標
/api/health
探針2.工具
3.告警
1.回滾
/api/health
:OK.env
無提交到 GitdbName
未設/帳密錯。awaitpage.waitFor...
。嘗試照這份手冊走,把 「韌性生活指南」 從可執行的程式,推到 可用、可維運、可持續迭代 的服務。
當使用者在颱風夜打開網站、三分鐘拿到專屬清單並完成採買時——這一整條 CI/CD 與部署鏈,就真正轉化成了 社會的韌性 。