跳至主要內容
Agentic Blog Image Pipeline

部落格圖片作業流水線 — 零成本、零帳單風險的完整方案

5 分

每次做靜態站,圖片管理都是讓人頭大的問題。

WordPress 年代有媒體庫,上傳、壓縮、CDN 全部包辦。切換到 Astro 之後,第一個踩到的坑就是:**圖片到底放哪裡?**我們在設計 Agentic Blog 的時候,列出了所有主流方案,逐一測試風險,最後走出一條零成本、零帳單風險的路。這篇文章把完整設計記錄下來。


我們踩過的坑,以及為什麼不用那些方案

在確定現在的架構之前,我們評估了以下選項:

Cloudflare R2 — 看起來免費,實際上有帳單風險。10 GB 免費,但一旦超過,每 GB 收 $0.015。更麻煩的是,R2 的帳單邏輯是「用了才知道」,監控設定繁瑣,對一個小 blog 來說不值得。

Git LFS — 這是社區公認的陷阱,後面會單獨說明。

Google Drive 直連 — 流量稍大就 403 限流。Google Drive 從來不是 CDN,不應該當 CDN 用。

imgix / Bunny CDN — 根本沒有免費 tier,直接排除。

Cloudinary — 有 25 credits 免費限制,小 blog 大概夠用,但既然 Cloudflare Pages 本身就能做到同樣的事,再多一個第三方帳號只是多一個管理負擔。

最後我們的答案是:Cloudflare Pages 的靜態檔案 CDN 就是我們需要的一切。 帶寬無限免費,全球邊緣節點,自動 HTTPS,不需要額外設定一個字。


1. 全局架構

整個圖片 pipeline 分成五層:來源、Google Drive SSoT、本地作業、Astro Build、CF Pages CDN。

flowchart TB
    subgraph 來源["圖片來源"]
        CANVA["Canva Pro 素材庫\n夏哥挑選"]
        GEMINI["Gemini API 生圖\n$0.039/張(備用)"]
        MANUAL["手動上傳\n截圖、照片"]
    end

    subgraph DRIVE["Google Drive SSoT"]
        STOCK["blog-assets/agentic/\nstock-library/\n001.webp ~ 210.webp\n(19.6 MB)"]
    end

    subgraph 本地["本地作業(SS1 Mac)"]
        PULL["rclone pull\n從 Drive 拉素材"]
        LOCAL_STOCK["public/images/\nstock-library/\n(.gitignore 不進 repo)"]
        SCRIPT["generate-cover.py\n--slug 或 --all"]
        MAPPING["cover-mapping.json\n文章↔素材配對表"]
        COVER_OUT["public/images/covers/\nagentic-{slug}-cover.png\n(1200×630)"]
        BODY_IMG["src/content/posts/\nattachments/\n(文內圖)"]
        SATORI["Satori OG Endpoint\nsrc/pages/og/[...slug].png.ts\n(1200×630 自動生成)"]
    end

    subgraph BUILD["Astro Build"]
        SHARP["Sharp 圖片優化\nPNG/JPG → WebP\n7 個響應式尺寸"]
        DIST["dist/\n部署產物"]
    end

    subgraph CDN["Cloudflare Pages CDN"]
        EDGE["全球邊緣節點\n免費無限帶寬\n自動 HTTPS"]
        READER["讀者瀏覽器\n最近節點取圖"]
    end

    CANVA -->|"下載 zip"| STOCK
    GEMINI -->|"API 輸出"| STOCK
    MANUAL -->|"上傳"| STOCK

    STOCK -->|"rclone pull"| PULL
    PULL --> LOCAL_STOCK
    LOCAL_STOCK --> SCRIPT
    MAPPING --> SCRIPT
    SCRIPT -->|"PIL 合成\n素材圖 + 暗色 overlay\n+ 標題 + 分類 pill\n+ 品牌水印"| COVER_OUT

    COVER_OUT -->|"進 repo"| SHARP
    BODY_IMG -->|"進 repo"| SHARP
    SATORI -->|"build-time\n自動生成"| DIST

    SHARP -->|"優化壓縮"| DIST
    DIST -->|"wrangler pages deploy\n或 git push → 自動 build"| EDGE
    EDGE --> READER

關鍵設計決策:素材圖(stock-library)不進 repo,透過 .gitignore 排除,只有最終合成的 cover PNG 才進 repo。這樣 repo 只存放真正需要版本控制的產出物,不會因為原始素材庫膨脹而拖累 git 效能。


2. 三種圖片類型

Agentic Blog 的圖片分三類,各自有不同的存放路徑、生成方式和 CDN 行為:

類型尺寸格式存放路徑進 repo生成方式
Cover 封面1200×630PNGpublic/images/covers/generate-cover.py
OG 社群分享1200×630PNGdist/og/(build 產物)Satori(build-time 自動)
Body 文內圖不限PNG/JPG/WebPsrc/content/posts/attachments/VaultCMS / 手動
Stock 素材1200×630WebPGoogle Drive否(.gitignore)Canva Pro 挑選

Cover 圖是唯一需要手動觸發生成的類型。OG 圖由 Satori 在 astro build 時自動生成,完全不需要人工介入。Body 圖直接放進 attachments/ 資料夾,由 Astro 的 Sharp integration 在 build 時優化成 7 個響應式尺寸。


3. Cover 生成流程

封面圖是整個 pipeline 中最有意思的環節。我們不用 AI 生圖(成本和一致性都是問題),而是從 Canva Pro 素材庫挑選 210 張高品質 WebP 作為原料,再用 generate-cover.py 批量合成風格一致的封面。

sequenceDiagram
    participant 夏哥
    participant Canva as Canva Pro
    participant Drive as Google Drive
    participant Agent as 小克 (Agent)
    participant Script as generate-cover.py
    participant Repo as Git Repo
    participant CF as CF Pages CDN

    夏哥->>Canva: 搜尋素材圖
    夏哥->>Canva: 挑選 + 下載 zip
    夏哥->>Drive: 上傳到 blog-assets/agentic/stock-library/

    Note over 夏哥: 以上一次性(已完成 210 張)

    夏哥->>Agent: 寫新文章
    Agent->>Agent: rclone pull Drive → 本地 stock-library/
    Agent->>Agent: 編輯 cover-mapping.json(配對素材編號)
    Agent->>Script: python3 generate-cover.py --slug {slug}

    Script->>Script: 載入素材圖(WebP/PNG)
    Script->>Script: Resize + Center Crop → 1200×630
    Script->>Script: 漸層暗色 Overlay(上淺下深)
    Script->>Script: 4px Terra Cotta 左色條
    Script->>Script: 分類 Pill(專案身份色)
    Script->>Script: 標題文字(PingFang 48px)
    Script->>Script: SUPERPORTIA AGENTIC 水印
    Script-->>Agent: 輸出 PNG → public/images/covers/

    Agent->>Repo: git add + commit
    Repo->>CF: push → 自動 build + deploy
    CF-->>夏哥: 文章上線,封面圖從最近邊緣節點載入

generate-cover.py 做的事情:

  1. cover-mapping.json 讀取 slug → 素材編號的配對
  2. 用 PIL 把素材圖 resize + center crop 到 1200×630
  3. 疊加漸層暗色 overlay(上方較透明、下方較深,讓文字可讀)
  4. 在左側畫 4px Terra Cotta 色條(品牌識別)
  5. 根據文章分類畫對應身份色的分類 pill
  6. 渲染標題文字(PingFang SC,48px)
  7. 加上 SUPERPORTIA AGENTIC 品牌水印
  8. 輸出到 public/images/covers/agentic-{slug}-cover.png

單次執行指令:

# 生成單篇文章的封面
python3 scripts/generate-cover.py --slug agentic-blog-image-pipeline

# 批量生成所有文章的封面(初次設定或素材更新後)
python3 scripts/generate-cover.py --all

# 從 Drive 拉取最新素材(需先設定 rclone)
rclone copy gdrive:blog-assets/agentic/stock-library/ public/images/stock-library/

命名規則保持一致:

類型格式範例
Stock 素材{NNN}.webp001.webp210.webp
Cover 封面agentic-{slug}-cover.pngagentic-egs-engineering-governance-spec-cover.png
OG 圖/og/{slug}.png/og/egs-engineering-governance-spec.png
Body 文內圖{描述}.{ext}mtaaa-architecture.png

4. 為什麼不用 R2、Git LFS、Google Drive 直連

這是整個設計中最重要的決策之一,值得詳細說明。

Warning

Git LFS 提供每月 1 GB 免費帶寬。問題是,這 1 GB 是所有協作者的 clone + fetch 合計消耗。 一個有 50 MB 圖片的 repo,只要 20 次 clone 就會超過免費額度,然後 GitHub 會自動向你的帳戶收費,沒有警告,沒有停止鍵。社區裡有無數人因為 Git LFS 的隱性帳單損失幾十到幾百美金。這不是誇張,是有據可查的設計缺陷。

方案表面優點真實風險
CF R210 GB 免費,零出口費超限自動收費,監控繁瑣
Git LFS原生 git 支援帶寬超限自動收費,社區公認陷阱
GitHub raw 直連不需額外設定2025 起限流嚴重,違反 ToS
Google Drive 直連免費流量稍大就 403,不穩定
CloudinaryCDN 功能完整25 credits 限制,多一個第三方帳號

CF Pages 靜態檔案 CDN 的優勢在於:超過限制時的行為是「拒絕部署」而不是「繼續服務並收費」。 這是本質上的安全差異。20,000 檔的免費上限到達後,wrangler 會報錯、部署停止,不會悄悄升級你的方案。


5. 費用分析:每個環節都是 $0

項目費用免費額度超限行為
CF Pages 帶寬$0無限不適用
CF Pages 檔案數$020,000 檔部署停止(不收費)
CF Pages 建構次數$0500 次/月排隊等待(不收費)
Google Drive 儲存$015 GB無法上傳(不收費)
PIL / generate-cover.py$0本地執行不適用
Satori OG 生成$0build-time不適用
Sharp 圖片優化$0Astro 內建不適用
總計$0/月零帳單風險

目前架構中,每一個超限事件的後果都是「服務停止」而不是「帳單增加」。這是我們選擇這套方案的核心原因:不需要盯著 billing dashboard,也不需要設定帳單警報。


6. 監控清單

雖然不會意外收費,但仍需要定期確認各項指標沒有接近警戒線:

監控項目指令免費上限警戒線超限後果處理方式
Git repo 大小git count-objects -vHGitHub 建議 < 1 GB500 MBpush 變慢搬圖到 R2
CF Pages 檔案數CF Dashboard → Pages20,000 檔15,000部署失敗減少 Sharp 尺寸或搬 R2
CF Pages 建構次數CF Dashboard → Pages500 次/月400排隊等待減少無效 push
Google Drive 空間Drive 設定頁15 GB12 GB無法上傳清理或升級
單張圖片大小ls -lh public/images/covers/CF Pages 限 25 MB/檔5 MB部署失敗壓縮
Build 時間astro build 輸出無硬限5 分鐘開發效率下降清 Sharp cache

建議每月跑一次以下腳本,輸出簡短的健康報告:

#!/bin/bash
# check-image-health.sh — 每月跑一次
echo "= Agentic Blog 圖片管線健康檢查 ="
echo ""
echo "Repo 大小:"
git count-objects -vH | grep size-pack
echo ""
echo "Cover 圖數量: $(ls public/images/covers/*.png 2>/dev/null | wc -l | tr -d ' ') 張"
echo "Stock 素材數量: $(ls public/images/stock-library/*.webp 2>/dev/null | wc -l | tr -d ' ') 張"
echo "Body 圖數量: $(find src/content/posts/attachments/ -type f 2>/dev/null | wc -l | tr -d ' ') 張"
echo ""
echo "public/images 總大小: $(du -sh public/images/ 2>/dev/null | cut -f1)"
echo ""
echo "最大的 5 張 Cover 圖:"
ls -lhS public/images/covers/*.png 2>/dev/null | head -5 | awk '{print $5, $9}'

7. 容量規劃

根據目前的發文節奏(每週 2-3 篇),我們做了以下估算:

階段文章數Cover 大小Body 圖估計Repo 總增量需要遷移?
現在(2026-03)2512.5 MB~5 MB~18 MB不需要
6 個月後10050 MB~30 MB~80 MB不需要
1 年後200100 MB~60 MB~160 MB接近考慮點
2 年後500250 MB~150 MB~400 MB考慮搬 R2

遷移觸發點:repo 超過 500 MB 或源圖超過 200 張時,搬到 CF R2(零出口費)。

CF R2 在遷移發生後依然是安全選項:零出口費意味著圖片被讀取不產生費用,超過 10 GB 免費額度才以 $0.015/GB 計費,而那個時候 blog 應該已經有足夠的商業理由支撐這點小額費用。


8. 我們的觀點

Tip

比 Cloudinary 簡單 — 不需要額外帳號,不需要 SDK,不需要 API key 管理。圖片就是靜態檔案,放進 repo,CF Pages 自動 CDN。

比 R2 安全 — R2 超限收費,CF Pages 超限停止部署。「停止」比「收費」安全,特別是對個人 blog 這種流量不可預測的場景。

比 Git LFS 可靠 — Git LFS 的帶寬帳單是開發者社區記錄最多的「驚喜帳單」來源之一。我們的方案把大型素材圖(stock-library)放在 Google Drive 並 .gitignore 掉,只有最終合成的 cover PNG 進 repo,大小可控。

CF Pages 免費 CDN 的持久性 — 這不是 Cloudflare 的隨機 free tier,是他們吸引開發者、建立生態系的戰略性產品。Cloudflare 的商業模式是賣企業方案,不是靠個人開發者的 CDN 費用。Pages 的免費 tier 已經穩定存在數年,風險遠低於同類競品。


工具清單

工具用途路徑 / 指令
generate-cover.py素材圖 → Cover PNGscripts/generate-cover.py --slug {slug}
cover-mapping.json文章↔素材配對scripts/cover-mapping.json
Satori endpointOG 圖 build-time 自動生成src/pages/og/[...slug].png.ts
rcloneDrive ↔ 本地素材同步rclone copy gdrive:blog-assets/agentic/stock-library/ public/images/stock-library/
Sharp(Astro 內建)Build-time 圖片優化,PNG/JPG → WebP × 7 尺寸自動(astro build
check-image-health.sh每月健康檢查bash scripts/check-image-health.sh

相關文件

...有限 未解決的問題: 圖片貼文是最大的缺口。12 位 KOL 裡至少有 4 位習慣用截圖分享觀點,這些內容目前完全無法進入 NLM 分析。解法需要 OCR + 圖片轉文字的前處理步驟,這是 [[Agentic Blog Image Pipeline]] 的延伸問題。 下一步 三個優先執行項目: 1. 建立 KOL-STT...

在此文章中被引用