Cloudflare Pages 的 20,000 檔案上限:當「一頁一檔」撞牆,我把 TaxMap 搬到 Netlify
本篇是「打造 TaxMap-TW:用 Astro 做台灣所得地圖」系列的第 9 / 10 篇。你可以從系列總覽開始閱讀,也可以直接接著看本文。
買了 bobochen.dev,興沖沖想把我做的「台灣所得地圖」TaxMap 掛上自己的網域。我用 Cloudflare 管 DNS,理所當然想直接丟 Cloudflare Pages。結果 pnpm build 完一看 dist/——23,331 個檔案。然後我才想起一件大家選部署平台時很容易忽略的事。
背景:把 TaxMap 搬上自己的網域,理所當然選 Cloudflare Pages
最近把主網域 bobochen.dev 買下來後,開始把各個 side project 掛到子網域底下。TaxMap-TW(台灣所得地圖,一個用 Astro 做的純靜態站)要放到 taxmap.bobochen.dev。
因為我 DNS 本來就在 Cloudflare,最自然的選擇就是 Cloudflare Pages——同一個後台、免費、CDN 又快。我連 astro.config.mjs 的 site 都已經指好 https://taxmap.bobochen.dev 了,想說 build 完上傳就收工。
撞牆:build 出來 23,331 個檔案
部署前我習慣先看一下產物大小。一看傻眼:
dist/共 880MB- 23,331 個檔案
我第一個反應是查單檔大小有沒有超標(Cloudflare Pages 單檔上限 25 MiB),結果最大的檔案才 5.7MB(villages.pmtiles),離 25 MiB 遠得很。容量、單檔都沒問題。
但真正擋我的是另一條限制,引用官方文件:
Cloudflare Pages sites can contain up to 20,000 files(Free 方案)
而我有 23,331 個。如果硬上傳,會吃到這個錯誤訊息:
Error: Pages only supports up to 20,000 files in a deployment.
擋我的不是 880MB,是「23,331」這個數字。 檔案大小和檔案數量是兩個完全獨立的天花板。
為什麼會爆檔案數?「一頁一檔」的代價
TaxMap 是「每個村里一個頁面」的資料密集型靜態站。台灣大約有 7,750 個村里,而我為每個村里都預先生成了三樣東西:
| 內容 | 檔案數 | 大小 |
|---|---|---|
| 村里頁 HTML | 7,747 | 272MB |
| 村里資料 JSON | 7,747 | 134MB |
| 村里 OG 社群分享圖 (PNG) | 7,750 | 466MB |
7,750 × 3 ≈ 23k 檔。光是給「分享你家村里所得」用的 OG 圖就 7,750 張。
這就是 SSG(靜態網站生成)「一頁一檔」模式的代價:內容一多,檔案數會線性爆炸。地圖、字典、商品目錄這類有大量 detail 頁的網站特別容易破萬。
我其實有三個選項
查清楚後,我整理出三條路:
- 升級 Cloudflare Pages 付費方案 — Free 是 20,000 檔,但付費方案可到 100,000 檔(這是 2026 起的新上限,得設
PAGES_WRANGLER_MAJOR_VERSION=4環境變數才會啟用),23,331 綽綽有餘。代價:要解鎖這個上限得開 Workers Paid,最低 $5/mo(注意不是網站那個 $20/mo 的 Pro 方案,當初我也一度被搞混)。 - 砍檔案數 — 不預生成那 7,750 張 OG 圖,改用 Worker 即時產生,檔案數降到 ~15,600 就能上 Free 版。代價:要改 OG pipeline。這個選項其實最漂亮——它不是「砍功能」,而是「換一種方式提供同樣的 OG 圖」,而且順便連未來的頻寬問題一起解了(圖只在被請求時才生成,不用整批塞進 CDN)。當下我嫌它要動工,但坦白說它是技術上最對的解。
- 換 Netlify — Netlify 沒有 Cloudflare Pages 這種「單次部署檔案數」硬限制,免費版就吃得下。代價:DNS 在 Cloudflare、站台在 Netlify,變成跨平台管理。
我選了 3。理由很務實:這是個人 hobby 專案,為了它每月多付錢、或為了遷就平台去重構 OG pipeline,當下都嫌麻煩;而那 7,750 張 OG 圖正是 TaxMap「分享你家村里」的病毒傳播核心,我不想動它。加上我另一個子網域(typelate)本來就在 Netlify,搬過去算順手。
但我得先說清楚:選 3 是「當下最快」的取捨,不是「客觀最優」。 選項 2(改 Worker 即時生成)其實才是長期最乾淨的解,只是要動工;如果你很在意運維一致性,DNS、站台、Workers 全留在 Cloudflare 一個後台,那付那 $5/mo 留在單一平台,其實是完全合理的選擇——跨平台管理的隱性摩擦(兩套後台、兩套憑證、兩套帳單)不是零成本,只是不會出現在帳單上。
解法:搬到 Netlify,5 分鐘搞定
因為資料和 OG 圖都已經 commit 在 public/(astro build 會自動複製到 dist/),所以根本不用跑那些慢的資料抓取腳本,純 build 就好:
pnpm build # 純 astro build → dist/
netlify deploy --prod --dir=dist # 直接上傳預先 build 好的 dist
中途遇到一次 getaddrinfo ENOTFOUND api.netlify.com——上傳到一半網路抖了一下。但 Netlify CLI 的部署是增量的,重跑一次它自動跳過已上傳的檔案、只續傳剩下的(23,330 → 22,455),第二次就成功了。
最後 https://taxmap.bobochen.dev 上線,Let’s Encrypt 憑證也自動簽好。
但 Netlify 不是「沒有天花板」,只是換了一面牆
我得誠實補一句,免得你照抄踩雷:搬到 Netlify 不等於「解除限制」,比較像是「把擋路的那道牆換成另一道」。Cloudflare Pages 卡我的是檔案數,但它的頻寬是沒有上限的;Netlify 反過來——沒有檔案數硬限制,但免費版頻寬是 100GB/月,超量大約每 100GB 收 $55。而 TaxMap 那 7,750 張 OG 圖正是設計來「被瘋狂分享」的,萬一哪天真的病毒傳播,最先撞牆的反而是 Netlify 的頻寬,不是 Cloudflare。換句話說,我換掉的是「檔案數天花板」,但同時換上了一個「頻寬天花板」——對一個指望靠分享擴散的站來說,這個取捨其實很微妙。
還有一個更新的時效雷:Netlify 從 2025-09 起改成 credit-based 計費,免費版每月有 300 credits 的硬上限,連 deploy 本身都會扣點,而且一個專案超量會把整個帳號的服務一起暫停(連坐)。我另一個子網域 typelate 跟 TaxMap 同一個 Netlify 帳號,這種連坐風險其實被我放大了。所以「免費、5 分鐘、無痛」這句話是我 2025-09 之前的體感——你的免費額度很可能跟我不一樣,動手前自己去對一遍當下的方案頁,別照抄我的數字。
反思
選靜態網站部署平台,大家通常只盯著三個數字看:單檔大小、總容量、頻寬。這次提醒我還有第四個——檔案數上限——而它最容易被忽略。我要先界定清楚:檔案數對 TaxMap 這種「一頁一檔、上萬個 detail 頁」的資料密集型站才會這麼致命,一般部落格、產品官網根本碰不到兩萬檔,那種站更該盯的是頻寬跟 build 時間。各家的檔案數天花板也差很多:Cloudflare Pages Free 是 20,000、付費才到 100,000,Netlify 則沒有這種等級的硬卡點。重點是選平台前把這四個限制都對一遍,免得中途翻車。
回頭看,我這次是 build 完、deploy 前才想到去查檔案數,算是運氣好,在浪費一次失敗部署之前就攔下來了。理想上這種限制應該在「選平台的當下」就查清楚,而不是撞牆才回頭——這也是我後來幫整個 TaxMap 專案做覆盤時記下來的一條。至於最後選免費平台 5 分鐘搬完、沒去重構 OG pipeline 也沒付月費,這算不上什麼大道理,純粹是一個 side project 該有的鬆弛感:不是每個技術潔癖都值得當下還債,能用最小力氣讓站上線、功能一個沒少,往往就夠了。
另外兩個踩坑時學到的東西:我一度以為「換成 Cloudflare Workers Static Assets 就能逃」,結果 Workers 靜態資源也有自己的檔案數上限,不是無腦解;還有就是這次最反直覺的地方——擋住你的可能不是「太大」,而是「太多」,880MB 完全沒事,是 23,331 這個「數量」把我卡死的。
官方限制文件:Cloudflare Pages Limits