Cloudflare Pages 教學:部署守門、線上驗收與退版
重點摘要
Cloudflare Pages 是 Cloudflare 的網站託管服務,免費方案每站最多 2 萬個檔、靜態檔案請求不限次數。用 wrangler 部署很快,但 Functions 從執行指令的目錄拿、部署完約 1 分鐘內正式網址還是舊版這類坑,要靠部署前守門、部署後線上實測和退版程序擋住。
Cloudflare Pages 是 Cloudflare 提供的網站託管服務,把建好的 HTML、CSS、圖片傳上去就能上線,需要伺服器端程式時再掛 Pages Functions,免費方案就放得下一般的公司官網、內容站和落地頁。要部署得穩,我們的做法是把建置、守門、部署、線上驗收寫成同一支腳本,任何一關沒過就停在上一版,出事時用 Cloudflare 保留的部署紀錄退回。
這篇的坑來自我們 2026 年 8 月到 10 月在自家與客戶的多個 Cloudflare Pages 專案上的實測,部署工具是 wrangler 4.119 與 4.148,作業系統是 Windows。官方規格以 2026 年 10 月 23 日查到的 Cloudflare 文件為準,免費額度之後可能會調整,上線前要再對一次。
Cloudflare Pages 是什麼?適合哪些網站
Cloudflare Pages 是 Cloudflare 的網站託管服務,主要放靜態網站:事先建好的 HTML、CSS、JavaScript 和圖片,上傳後由 Cloudflare 的全球網路送給訪客,不用自己顧主機。需要表單、轉址這類伺服器端功能,可以加跑在 Cloudflare Workers 上的 Pages Functions。公司官網、內容站、落地頁都適合,需要常駐程式的服務就不適合。
拿 Cloudflare Pages 免費架站,要先看清楚這幾個數字(2026 年 10 月 23 日查官方文件):
| 項目 | 免費方案 | 官方出處 |
|---|---|---|
| 每站檔案數 | 20,000 個 | Pages 限制 |
| 單一檔案大小 | 25 MiB | 同上 |
| Git 連動的建置次數 | 每月 500 次,一次只能建 1 個 | 同上 |
| 每個專案的自訂網域 | 100 個 | 同上 |
| 靜態檔案請求 | 不限次數、免費 | Functions 計價 |
| Functions 請求 | 跟 Workers 共用每天 100,000 次,UTC 午夜重置 | 同上 |
| Functions 每次請求的 CPU 時間 | 10 毫秒 | Workers 限制 |
| KV 讀取、寫入、列出 | 每天 100,000、1,000、1,000 次 | KV 限制、KV 計價 |
官方對「靜態」的定義是:沒有叫用 Functions 的請求。依這個定義,middleware 掛在全站時每個請求都會叫用 Functions,全部算進每天 10 萬次的額度。純靜態網站幾乎碰不到上限,掛了全站 middleware 的網站,要另外估一下每天的請求量。
部署方式:Git 連動、wrangler、拖拉上傳差在哪
Cloudflare Pages 有三種部署方式:接 GitHub 或 GitLab,讓 Cloudflare 每次推送後自己建置;在本機或排程主機建好網站,再用 wrangler 指令上傳;或在後台把資料夾直接拖進去。三種的檔案上限和能不能帶 Functions 都不一樣,我們的網站幾乎都用 wrangler,因為建置後、上傳前要先跑自己寫的檢查,檢查沒過就不上傳。
| 方式 | 怎麼做 | 檔案上限 | 能不能帶 Functions | 適合 |
|---|---|---|---|---|
| Git 連動 | 推到 GitHub/GitLab,Cloudflare 幫你建置 | 20,000 個 | 可以 | 用框架、建置流程單純的專案 |
| wrangler pages deploy | 自己建好,再用指令上傳資料夾 | 20,000 個,單檔 25 MiB | 可以,從執行指令的目錄拿 | 上傳前要跑自己的檢查 |
| 後台拖拉上傳 | 在後台把資料夾拖進去 | 1,000 個,單檔 25 MiB | 不行 | 一次性的小頁 |
wrangler 這一列的「從執行指令的目錄拿」最容易出事。官方的 Direct Upload 文件寫,用 wrangler 部署時,「if a functions folder exists where the command is run」,那個 functions 資料夾就會跟著上傳;Functions 入門文件也說,functions 資料夾要放在專案根目錄,不是放在 dist 這種輸出資料夾裡。換句話說,你要部署的資料夾裡就算有 functions,wrangler 也不看。
wrangler 從哪裡拿 Functions
我們的上線流程:建置、守門、部署、線上驗收
照 Cloudflare Pages 教學把指令跑完、網址打得開,只是上線的一半,另一半是部署前後的檢查。我們的每個網站都只有一支部署腳本,依序跑建置、守門、產生 sitemap、部署、線上驗收、通知搜尋引擎,任何一關回傳非 0 就整條停下,網站停在上一版。這支腳本本身也做過負面測試:故意弄壞一個檔案,確認它真的會停、而且沒有部署出去。
一支腳本跑完的上線流程
- 建置從同一份原始檔產出整站,CSS 版本號用內容雜湊
- 守門連結、結構化資料、檔案白名單,任一項沒過就停
- 產生 sitemap日期只跟著內容走,不跟建置時間
- 部署從乾淨的暫存目錄執行 wrangler,只放本專案的 functions
- 線上驗收用部署專屬網址比對內容、標頭、轉址
- 通知搜尋引擎驗收過了才送 IndexNow
一定要寫成一支腳本,是因為手動串起來的指令,前一個失敗不會擋住下一個:守門回傳失敗,wrangler 照樣會把網站推上去。
守門要擋什麼,我們是一次一次踩出來的:
- 檔案白名單。 wrangler 會把部署資料夾整包推上線,只自動跳過 .DS_Store、.git、node_modules、.wrangler。2026 年 10 月 21 日,某內容站資料夾裡的圖片審核清單、備份檔、145 張產圖原稿,線上全部回 200;.env 放進去也一樣會被推上去。我們改成副檔名白名單,不在名單上的檔案就擋部署。
- 內嵌 CSS 的註解。 建站程式把 CSS 原封不動內嵌進每頁,檔案裡的改版註解出現在 191 頁的原始碼裡,內容守門只看正文所以沒擋。現在內嵌前先剝掉註解,再驗每頁的 style 裡沒有註解符號。
- CSS 與 JavaScript 的版本號。 改了 CSS 但網址上的
?v=沒跟著改,加上快取 4 小時,回訪的人最長 4 小時看舊版;全新的瀏覽器永遠測不出來,因為它沒有舊快取。版本號改用檔案內容雜湊,寫進建置流程。 - 守門本身。 守門沒報錯,不代表它真的有在查。每支守門寫完都要故意餵它錯的東西,確認它會叫;死連結守門的負面測試寫在 soft 404 與部署閘門,我們開源的 contentgate 內容守門工具也是同一套想法。
部署完要驗什麼?線上實測清單
部署指令顯示成功,只代表檔案傳上去了,不代表線上就是你要的樣子。我們部署完會用程式逐項驗:Functions 有沒有跟著上去、內容是不是新版、標頭規則有沒有命中、不存在的網址是不是回 404、自訂網域與憑證是否生效。每一項都對應一次真的出過的事,驗不過的部署要當成失敗處理。
| 驗什麼 | 怎麼驗 | 沒驗會怎樣 |
|---|---|---|
| Functions 有沒有上去 | 輸出要有「Uploading Functions bundle」這行,部署 API 回的 uses_functions 要是 true | 兩個站的 www 網址從 301 變 200,整站多一份複本 |
| 內容是不是新版 | 用部署專屬網址(8 碼.專案名.pages.dev)驗,或等 1 分鐘以上 | 剛部署完約 1 分鐘內,正式網址還在給舊內容 |
| 本機與線上一致 | 用位元組比對,先剝掉 Cloudflare 自訂網域插入的統計 script | 沒剝乾淨時 246 頁全部誤判不一致 |
| 標頭規則有沒有命中 | curl 看回應標頭,目錄型網址要寫 /:slug/ |
以為全站關快取,其實只有首頁生效 |
| 不存在的網址 | 亂打一個網址,要回 404 | 沒有 404.html 時,任何網址都回 200 加首頁 |
| 檢查程式沒被擋 | 自己的檢查程式帶自訂的 User-Agent | Python 預設的 UA 會被 Cloudflare 擋(403 或 1010),誤判成網站壞了 |
| 自訂網域 | 用 API 綁網域後確認 DNS 有 CNAME | zone 裡 0 筆記錄,網域打不開 |
幾項要多說明。_headers 的寫法,官方文件說明可以用 :placeholder_name 這種佔位符比對路徑;我們實測 /slug/ 這種目錄型網址,寫 /、/*.html、/*/ 都不會命中,要寫 /:slug/。某站以為全站關了快取,其實只有首頁生效,40 次取樣有 5% 拿到舊版,看起來很像部署還沒傳完。
沒有 404.html 的問題,官方的 Serving Pages 文件寫得很清楚:專案頂層沒有 404.html,Pages 會當成單頁應用程式處理。它對搜尋的影響和修法,寫在 soft 404 正在白燒你的爬取預算。Cloudflare Pages 還會把 .html 網址轉到無副檔名的網址,canonical 和 sitemap 要寫成同一種形式,見新站沒被索引的檢查。
自訂網域方面,官方文件說,在後台加網域、網域又已經在 Cloudflare 上時,確認後會自動加 CNAME;但沒先在 Pages 專案裡綁定就自己加 CNAME,會出現 522 錯誤。我們 2026 年 8 月 30 日用 API 綁網域時發現,API 這條路不會自動建 DNS,zone 裡是 0 筆記錄,要手動建 CNAME,建好後憑證約 40 到 70 秒生效。
最後是預覽網址。官方的預覽部署文件寫,每個預覽部署預設都帶 X-Robots-Tag: noindex,不會被搜尋引擎收錄;但「專案名.pages.dev」這個正式預設網址不會自動 noindex,自訂網域上線之後,它就是整站的完整複本。我們的做法是 canonical 一律寫正式網域,有的專案再用 Functions 把 pages.dev 轉到正式網域。
退版:每次部署都留一個網址
Cloudflare Pages 每次部署都會留下自己的網址,退版不用重新建置。官方的退版文件寫,只要是建置成功的正式部署,都可以當退版目標,在後台的部署清單選「Rollback to this deployment」,確認後正式網址立刻換回那一版;預覽部署不能拿來退版。退回之後,比目前更新的版本也還是可以再選回來。
部署紀錄還有兩個用處。一是查「Google 當時抓到什麼」:部署歷史 API 每頁最多 25 筆,超過會回 400,可以逐版打開舊網址看當時的內容。二是量改版前的速度:舊版網址還活著,修完速度要比較時,可以拿舊版和新版用同一種量法各量一次。我們的部署腳本失敗時,會直接印出一行退回上一版的指令,出事的人不用臨時查文件。
「部署失敗」也要分清楚是哪一種,因為處理方式完全不同。我們的通知固定分三種寫:
| 失敗發生在 | 線上現在是哪一版 | 要做什麼 |
|---|---|---|
| 上線前的檢查沒過 | 還是上一版 | 修內容,重跑腳本 |
| 上傳失敗 | 還是上一版,沒有更新 | 查網路或權限,重跑部署 |
| 已上傳,但線上驗收沒過 | 已經是新版 | 判斷影響,必要時馬上退版 |
Cloudflare 缺點與限制:Functions、KV 和 wrangler
Cloudflare Pages 的免費額度對靜態網站很夠用,會碰到限制的多半是 Functions 和 KV 這些伺服器端功能,以及 wrangler 在 Windows 上的小毛病。我們撞過四個:Functions 每次請求只有 10 毫秒 CPU、KV 列出操作每天只有 1,000 次、用 API 設的環境變數會默默消失、wrangler 在中文路徑會當掉。
Cloudflare Workers 的 CPU 時間。 免費方案每次請求只有 10 毫秒 CPU,付費方案預設 30 秒、最多 5 分鐘。2026 年 9 月 11 日,我們在一個跑在 Cloudflare 免費方案上的後台測密碼雜湊(PBKDF2):迭代 10 萬次可以登入,15 萬次就回 500,錯誤碼 1101;OWASP 建議的 60 萬次,在免費方案上做不到。更麻煩的是,用不存在的帳號測登入會得到「正常」的假象,因為那條路走的是另一個低成本的流程。官方文件寫 CPU 超限會回錯誤 1102,我們那次看到的是 1101,所以只看錯誤碼判斷不了是不是 CPU 超限,要用階梯測試找上限。
KV 的列出次數。 KV 免費方案讀取每天 10 萬次,列出(list)只有 1,000 次。2026 年 9 月 10 日我們有一支 Worker 用 list 讀名單,額度打爆後整支 Worker 回錯誤 1101,到台北時間早上 8 點(UTC 0 點)才重置。名單類資料改成維護一個索引 key,用 get 讀,就不會再撞到。
環境變數。 用 Cloudflare API 設 Pages 的環境變數時,type 寫 plain_text 會回 success,回應裡也列出變數名稱,但重新讀取就不見了,程式拿到的是空值。我們一律改用 secret_text,設完一定重新讀回來比對;環境變數改了之後,要重新部署一次才生效。
wrangler 與中文路徑。 wrangler 4.119 在中文路徑下直接當掉,退出碼 0xC0000409,同一個指令在英文路徑就成功。wrangler 4.148 在中文路徑部署其實成功、線上也換了,退出碼卻是 3221226505,會讓看退出碼的腳本誤判成失敗。台灣的團隊很常把專案放在中文資料夾,部署用的暫存目錄一律改成英文路徑。
實測數據:我們在 Cloudflare Pages 量到的
我們 2026 年 8 月到 10 月在 Cloudflare Pages 與 Workers 上量到的結果,多數是單一網站、單次事件,有些是兩個站同時中招。數字是當時的版本與方案,wrangler 版本更新後行為可能會變,引用時請連同日期一起看。
| 項目 | 做法 | 結果 | 條件、日期、樣本數 |
|---|---|---|---|
| wrangler 執行目錄 | 從沒有 functions/ 的目錄部署 | 顯示 Deployment complete,那一版卻沒有 middleware,www 網址從 301 變 200 | 2 個站,2026-09-22 |
| 部署後的正式網址 | 部署完馬上掃正式網址 | 約 1 分鐘內還是舊內容,部署專屬網址已是新版 | 1 個站,2026-09-24 |
| _headers 規則 | 寫 /、/*.html、/*/ 想涵蓋目錄型網址 |
只有首頁生效,40 次取樣有 5% 拿到舊版 | 1 個站,2026-08 |
| 本機與線上比對 | 文字模式讀檔、沒剝 Cloudflare 插入的 script | 246 頁全部誤判不一致 | 1 個站,2026-09-28 |
| 部署資料夾 | wrangler 整包上傳 | 審核清單、備份檔、145 張產圖原稿線上全部回 200 | 某內容站,2026-10-21 |
| 內嵌 CSS | 建站程式原封不動內嵌 | 改版註解出現在 191 頁的原始碼 | 1 個站,2026-09-25 |
| Workers CPU | PBKDF2 迭代次數階梯測試 | 10 萬次可登入,15 萬次回 500(錯誤碼 1101) | 免費方案,2026-09-11 |
| KV list() | 免費方案用 list 讀名單 | 每天 1,000 次,打爆後整支 Worker 回 1101 | 1 支 Worker,2026-09-10 |
| 環境變數 | API 設 plain_text | 回 success,重新讀取就不見 | 1 個專案,2026-09-14 |
| 自訂網域 | 用 API 綁網域 | zone 0 筆記錄要手動建 CNAME,憑證 40~70 秒生效 | 我們的官網,2026-08-30 |
| wrangler 中文路徑 | 4.119 與 4.148 | 4.119 當掉(0xC0000409);4.148 部署成功但退出碼 3221226505 | Windows,2026-09-24/2026-10-07 |
部署踩過的坑:現象、原因、怎麼修
這些坑的共同點是「指令說成功、畫面看起來正常」:wrangler 印出 Deployment complete、API 回 success、守門亮綠燈,問題卻已經在線上。我們 2026 年 8 月到 10 月在多個 Cloudflare Pages 專案上各踩過至少一次。
www 網址突然變成整站複本 現象:2026 年 9 月 22 日,兩個站的 www 網址從 301 轉址變成 200,整站被複製一份,wrangler 照樣印出 Deployment complete。 原因:wrangler 從執行指令的目錄拿 functions,那次是從沒有 functions 的目錄部署,那一版完全沒有 middleware。 怎麼修:每次建一個乾淨的暫存目錄,只放本專案的 functions,在那裡執行 wrangler;輸出沒有「Uploading Functions bundle」就判定部署失敗。
別的專案把我的網址轉走 現象:某站部署之後,網址被轉到另一個網站去。 原因:多個專案共用同一個部署目錄,其中一支部署腳本會先把共用的 functions 整個刪掉、換成自己的,下一個在那裡部署的站就帶著別人的轉址規則上線。 怎麼修:不共用部署目錄,每個專案用自己的乾淨暫存目錄;部署後驗「預設網址不可以被轉走」。
驗收驗到舊版 現象:剛部署完就掃正式網址,全部通過,事後發現掃到的是舊內容。 原因:部署完約 1 分鐘內,正式網址還在給舊內容,部署專屬網址已經是新的。 怎麼修:驗收一律用部署專屬網址,或等 1 分鐘以上,再跟本機建站產物逐頁比對。
本機和線上永遠對不上
現象:比對本機產物和線上網頁,246 頁全部顯示不一致。
原因:Python 文字模式讀檔會把 CRLF 轉成 LF,Cloudflare 自訂網域又會在 </body> 前插入一行統計 script。
怎麼修:用位元組比對,先剝掉 Cloudflare 插入的那一行再比。
正式資料被推回一週前 現象:從本機部署一個資料站,線上的資料倒退了 7 到 9 天。 原因:正式資料只在雲端主機上更新,本機是舊快照。 怎麼修:資料站只從雲端主機部署,本機部署前先同步,建站時沒有最新資料就擋。
手機版的修正,使用者看不到
現象:CSS 修好、部署成功,全新瀏覽器測也正常,回訪的人看到的還是舊版。
原因:HTML 引用的 ?v= 版本號沒跟著改,快取 4 小時內瀏覽器一直用舊檔。
怎麼修:版本號用 CSS、JS 檔的內容雜湊,建置時自動改寫,並驗每頁引用的版本號等於實際雜湊。
我們的方案:網站上線、部署守門與維運
我們幫客戶把網站放上 Cloudflare Pages 時,交付的是整條上線流程:建置、守門、部署、線上驗收、退版指令寫成同一支腳本,守門都做過負面測試,部署後用部署專屬網址自動驗收。網站上線、部署守門、監控與維運依功能報價,評估不收費,可以從自動化與網站維運聯絡我們。
網站如果要靠搜尋帶流量,上線流程通常跟技術 SEO 一起做:canonical、sitemap 日期、404、結構化資料都在守門裡。SEO 建置 $900 USDT、月維護 $400 USDT/月,方案在價格頁;sitemap 日期怎麼做寫在 sitemap lastmod 的內容雜湊做法,更多實作整理在 SEO 與 GEO 知識庫。
常見問題
Cloudflare Pages 免費嗎? 可以免費用。免費方案每站最多 20,000 個檔、單檔 25 MiB、Git 連動每月 500 次建置,靜態檔案請求不限次數;用到 Functions 時,請求跟 Workers 共用每天 100,000 次的額度,每次請求 CPU 時間 10 毫秒。
Cloudflare Pages 和 Cloudflare Workers 差在哪? Pages 放網站檔案,Workers 跑程式。Pages 專案裡的 Functions 其實就跑在 Workers 上,請求也算進 Workers 的額度;純靜態網站用 Pages 就夠,需要伺服器端邏輯時再加 Functions。
用 wrangler 部署,為什麼 Functions 沒有上去? 多半是執行指令的目錄不對。wrangler 拿的是執行指令那個目錄底下的 functions 資料夾,不是你要部署的資料夾裡的;部署完要看輸出有沒有「Uploading Functions bundle」,以及部署 API 回的 uses_functions 是不是 true。
部署錯了怎麼退回上一版? 在後台的部署清單選要退回的那一版,按「Rollback to this deployment」,正式網址會立刻換回去。只有建置成功的正式部署能當退版目標,預覽部署不行。
pages.dev 網址會被 Google 收錄嗎? 預覽部署不會,官方預設都帶 X-Robots-Tag: noindex;但「專案名.pages.dev」這個正式預設網址不會自動 noindex。自訂網域上線後,canonical 要寫正式網域,或用 Functions 把 pages.dev 轉過去。