Engineering

Hao0321 Studio 的技術堆疊:1 人工作室的全端架構

2026 年 4 月 21 日 約 8 分鐘閱讀 作者:Hao0321 Studio

做獨立工作室最容易踩的雷,就是在技術選擇上花太多時間討論「用什麼才對」,最後一行程式碼都沒寫。我花了一段時間實際把工作室官網、14 款瀏覽器遊戲、後端 API 全部上線,累積下來的架構非常簡單,全部加起來月固定成本不到台幣 200 元,一個人週末就能 debug 完。這篇把全站技術堆疊完整攤開,當成給未來想做類似規模專案的朋友(以及半年後忘記自己用什麼的我)的一份備忘錄。

為什麼要寫這篇

坊間的「獨立工作室技術堆疊」文章通常不是太抽象(「選你熟的就好」),就是太 enterprise(「先導入 Kubernetes、Terraform、OpenTelemetry」)。我想寫一篇夾在中間的 — 具體到 每個組件選什麼為什麼選它實際跑起來花多少錢一個人怎麼維護。這份架構撐起了:

全站架構地圖

三個獨立但互相串接的層:

[Browser]
    ↓
[ Cloudflare CDN / Pages ]  ←  靜態內容(HTML / JSX / 圖片)
    ↓
[ Cloudflare Workers ]       ←  API 端點(JS 邊緣運算)
    ↓
[ Cloudflare D1 ]            ←  SQLite on the edge

沒有 Kubernetes、沒有 Docker、沒有自己架的 VPS、沒有 MongoDB 或 PostgreSQL。每個請求的生命週期大概是:使用者在瀏覽器開啟頁面 → Cloudflare Pages 從邊緣節點吐靜態檔 → JS 呼叫 Workers API → Worker 讀寫 D1 → 一路 SSL 終端都在 Cloudflare 做掉。在台北,API 回應時間大約 50~90ms

前端(Frontend)

主站:純 HTML + Vanilla JS

主站 hao0321.com/index.html 是一個 1600 行的單檔 HTML。沒有 React、沒有 build step、沒有 bundler。理由很樸素:

缺點當然有:CSS 和 JS 都寫在同一個檔案裡、重複結構要手動複製、跨頁共用邏輯得手動同步。但站的規模是 6 頁,這些成本可以忍。

遊戲大廳與遊戲本體:React via Babel(在瀏覽器裡)

每款遊戲是一個 .jsx 檔案,由 play.html 動態 fetch 後用 Babel Standalone 即時轉譯再 eval。完整流程:

fetch('cat-battle.jsx')
  .then(r => r.text())
  .then(raw => {
    const { code, defaultExport } = stripModuleSyntax(raw);
    const transformed = Babel.transform(code, { presets: ['react'] }).code;
    eval(finalCode);
  });

這個做法看起來很粗暴(「Babel 在瀏覽器裡跑 = 慢」),但實際延遲可以接受:React + Babel 加起來約 200KB gzipped,第一次載入花 300ms,切換遊戲時的 Babel 轉譯大約 50~150ms。換取的好處是:

權衡:production 流量大時這不是最佳解,應該 pre-compile。但當前工作室規模,這個成本可以吸收。

字型與視覺一致性

全站用 Plus Jakarta Sans(標題、英文)、Noto Sans TC(中文)、Playfair Display(特殊強調)、JetBrains Mono(等寬)。色彩系統統一由 CSS custom properties 管理,每個頁面都複製同一套 :root 變數表 — 醜但穩定。

後端(Backend)

Cloudflare Workers

整個後端是一個 203 行的 worker.js 檔案。API 涵蓋:

MethodPath用途
POST/auth/googleGoogle ID token → JWT
GET / POST/checkin每日簽到
POST/play匿名 +1 遊玩次數
POST/score提交分數、更新 best_scores
GET/leaderboard[/:game]全域或單款遊戲排行榜
GET/profile個人檔案 + 最佳紀錄 + 成就
GET/achievements成就清單

選 Workers 的理由:

資料庫:Cloudflare D1

D1 是 Cloudflare 的 SQLite-on-edge 產品。Schema 只有 5 張表:userscheckinsscoresbest_scoresplay_countsachievements。沒有 ORM,直接寫 parameterised SQL:

await env.DB.prepare(
  'SELECT b.score, u.name, u.avatar FROM best_scores b ' +
  'JOIN users u ON b.user_id = u.id ' +
  'WHERE b.game_id = ? ORDER BY b.score DESC LIMIT 20'
).bind(game).all();

對於遊戲後端這種讀多寫少的場景,SQLite 完全夠用。D1 自動處理備份、複製、版本化遷移。整個資料庫目前佔 57KB,距離 10GB 的 free tier 上限還有一大段。

認證(Auth)

不自己管密碼,全靠 Google Sign-In。流程:

  1. 前端載入 Google Identity Services SDK,渲染「Sign in with Google」按鈕。
  2. 使用者授權後,Google 回傳一個 ID token(JWT 格式)。
  3. 前端把 ID token 打到 /auth/google
  4. Worker 呼叫 Google tokeninfo 端點驗證 token 有效,並檢查 aud(必須等於我們的 GOOGLE_CLIENT_ID)、exp(未過期)、email_verified
  5. Worker 寫入 D1 users 表(若是新使用者),用自己的 JWT_SECRET 簽一個 30 天期限的 session JWT 回給前端。
  6. 前端把 session JWT 存 localStorage,之後所有 API 請求 header 帶 Authorization: Bearer <jwt>

JWT_SECRETwrangler secret put 存在 Cloudflare Workers 的加密環境變數,從不寫進原始碼或 wrangler.toml。先前踩過一次把 placeholder secret 寫進 wrangler.toml 推到 GitHub 的坑,立刻 rotate 換掉。

部署(Deploy)

三個獨立的 Cloudflare Pages / Workers 專案:

專案網域部署指令
hao0321-studiohao0321.comwrangler pages deploy . --project-name=hao0321-studio
hao0321-gamesgame.hao0321.comwrangler pages deploy ./game --project-name=hao0321-games
hao-games-apiapi.hao0321.comwrangler deploy(在 game-api/ 內)

Pages 是「直接上傳」模式(不是 Git 整合),所以 git push 不會自動部署,要額外手動執行 wrangler。這在實務上有個好處:push 到 main 可以當成「階段性備份」,真正 production 上線要經過一次額外的手動動作,減少誤推的機會。

成本實算

目前所有 Cloudflare 服務都在免費額度內:

服務用量免費上限
Pages 請求每月約 3000無上限(fair use)
Workers 請求每月約 1500100 萬 / 月
D1 讀取每月約 5000500 萬 / 月
D1 寫入每月約 20010 萬 / 月

唯一付費的是 hao0321.com 網域年費(Cloudflare Registrar,約 US$10 / 年 ≈ 台幣 310 / 年 ≈ 每月 26 元)。其他全免費。所以實際月成本 新台幣 26 元。當流量增加需要開始付費時,100 萬次 Workers 請求的下一階是 $5 USD — 到那時候再煩惱。

一個人怎麼維護

關鍵在於降低「變更」的摩擦

不適合這套架構的情境

誠實說這套不是銀彈:

結語

技術選擇的第一性原則應該是「這個決定把問題變簡單了還是變複雜了」。對一個要做作品集、遊戲、內容網站的獨立工作室,Cloudflare 全家桶的答案是「變簡單很多」。省下來的時間用來做遊戲、寫文章、把視覺做細緻,比多花時間調 infra 有意義得多。

如果你也在設計小型個人專案或獨立工作室的技術架構,歡迎參考這份地圖。有問題隨時寄信 lo246179268@gmail.com 聊。