---
slug: cloudflare-pages-site-metrics-api
title: 如何在 Cloudflare Pages 上使用流量統計功能 (基於 Cloudflare API ）
status: published
excerpt: 靜態部落格也能顯示即時流量與 AI crawler 造訪數。重點是把 Cloudflare token 留在 Pages Function，前端只打同源 API。
category: Cloudflare
tags: [cloudflare, pages, analytics, api, graphql, workers]
author: Seer
author_role: Author
read_time: 8 min
cover: "/static/cloudflare-pages-site-metrics-api-cover-v2.png"
closing_note: "最安全的代理，是只傳遞數據而不留下任何多餘的權限漏洞。"
published_at: "2026-05-28T00:00:00Z"
updated_at: "2026-07-01T15:54:39Z"
---

上個月我在幫部落格加上一個「即時流量統計」與「AI 爬蟲造訪次數」的側邊欄元件時，遇到了一個麻煩：既然整個部落格是部署在 Cloudflare Pages 上的純靜態網頁（Static Site），我該怎麼在不洩露 API token 的前提下，撈到後台的 GraphQL Analytics 資料？這篇文章記錄了我如何利用 Pages Functions 當成 Proxy，在同源（Same-origin）的安全保護下，把 Cloudflare GraphQL 的流量統計資料安全地送到前端展示。

這個流程的通訊邏輯很單純：
```text
靜態網站部署到 Cloudflare Pages 之後，透過把 token 放在 Pages 的環境變數，然後用 Pages Function 做一層同源 API proxy 做轉發。
透過轉發帶 token 去查 Cloudflare GraphQL Analytics API。
```
這個流程圖簡單說明了前端與後端的通訊邏輯：前端打同源 API，由 Pages Function 幫忙帶上敏感的 token，再去背後的 Cloudflare API 撈資料。

這樣做能維持純靜態部署的優點，不需要維護任何實體伺服器，又能透過 Edge Function 即時跟 Cloudflare 的統計後台撈資料。

---

## 這個架構需要準備哪些環境變數？

在極簡的架構下，你只需要準備以下兩個參數：

```text
CF_API_TOKEN
CF_ZONE_ID
```
這兩個是 Cloudflare Pages 專案裡最重要的環境變數，一個是帶有 Analytics 讀取權限的 token，另一個則是你的網域 ID。

`CF_API_TOKEN` 請務必設定在 Cloudflare Pages 的 Encrypted secret。這個 token 所需的最低權限為 `Account Analytics: Read` 即可。千萬不要把 token 直接寫進 `static/`、`public/`、前端 JS 或 commit 到 GitHub 裡。

`CF_ZONE_ID` 則是你的網域對應的 Zone tag（例如 `seer.md` 在後台的 ID）。這雖然不是密碼，但放在 environment variables 裡一起管理會比較乾淨，Function 在 runtime 也可以少跑一次 Zone 的查表 request。

另外，你還可以透過這些選填的參數做進一步微調：

```text
CF_ACCOUNT_ID
CF_ZONE_NAME
SITE_METRICS_WINDOW_HOURS=24
SITE_METRICS_CACHE_SECONDS=300
SITE_METRICS_AI_USER_AGENTS=GPTBot,ChatGPT-User,OAI-SearchBot,ClaudeBot,PerplexityBot
SITE_METRICS_SEVEN_DAY_MAX_DAYS=7
```
這些是額外的選填環境變數，用來微調快取時間、抓取的 AI 爬蟲 User-Agent 清單、以及時間視窗大小。

---

## 在 Cloudflare Dashboard 上要怎麼設定？

打開 Cloudflare Pages 後台，點擊路徑如下：

```text
Workers & Pages
→ 選擇你的 Pages 專案
→ Settings
→ Variables and Secrets
→ Add
```
這是 Cloudflare 後台的點擊路徑，照著這個順序就可以把我們剛才定義好的 secrets 和 variables 填進去。

填入時建議的變數屬性如下：

```text
CF_API_TOKEN                 Secret / Encrypt
CF_ZONE_ID                   Variable
SITE_METRICS_CACHE_SECONDS   Variable
SITE_METRICS_AI_USER_AGENTS  Variable
```
這裡展示了這幾個環境變數的加密型態，像是 API Token 必須設為 Secret 加密，而快取時間等參數設為一般的 Variable 即可。

請記住，設定完環境變數之後需要「重新部署（redeploy）」一次專案才會生效。部署時，這些變數會被掛載到 Pages Function 的 `context.env` 底下。

---

## 怎麼實作 Pages Functions 的 API Proxy 轉發？

在 Cloudflare Pages 中，Functions 資料夾下的檔案路徑會自動對應到路由網址。在我的專案中，檔案放在這裡：

```text
functions/api/site-metrics.js
```
這是 Cloudflare Pages Function 的約定路徑（Convention），只要在這個位置下建檔案，它就會自動被部署成一個 API endpoint。

部署完成後，前端就可以直接打這個網址：
`https://blog.seer.md/api/site-metrics`

我們在 React 元件或前端 Vanilla JS 中，可以這樣非同步讀取資料：

```js
const response = await fetch("/api/site-metrics?range=7d", {
  headers: { Accept: "application/json" }
});
const metrics = await response.json();
```
這段是前端 React 或 Vanilla JS 呼叫同源 API 的寫法，直接用相對路徑 fetch，完全不需要帶任何 API token 或處理跨網域（CORS）問題。

而在 Pages Function 後端，程式碼只做三件事：
1. 從 `env` 讀取 Token、Zone ID 以及 Cache 時間設定。
2. 組裝 GraphQL 查詢，用 `fetch()` 以 POST 方法打 Cloudflare 的 Analytics 後台。
3. 把撈回來的複雜資料整理成簡化版的 JSON，回傳給前端。

Cloudflare GraphQL 的服務端點固定為：
`https://api.cloudflare.com/client/v4/graphql`

轉發時帶上我們隱藏在環境變數的 `CF_API_TOKEN`：

```js
await fetch("https://api.cloudflare.com/client/v4/graphql", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${env.CF_API_TOKEN}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ query, variables })
});
```
這段是 Pages Function 後端向 Cloudflare GraphQL 伺服器打 API 的實作，此時 Authorization header 帶的 Bearer Token 是由環境變數注入，前端完全接觸不到。

---

## 如何寫 GraphQL 查詢 visits 次數？

為了在部落格顯示基本的 Visits 人次，我們需要在 GraphQL 裡撈 `httpRequestsAdaptiveGroups`：

```graphql
query SiteMetrics($zoneTag: string, $start: Time, $end: Time) {
  viewer {
    zones(filter: { zoneTag: $zoneTag }) {
      total: httpRequestsAdaptiveGroups(
        limit: 1
        filter: {
          datetime_geq: $start
          datetime_lt: $end
          requestSource: "eyeball"
          edgeResponseStatus_geq: 200
          edgeResponseStatus_lt: 400
        }
      ) {
        count
        sum {
          visits
        }
      }
    }
  }
}
```
這是 GraphQL 查詢語法，我們限定 `requestSource` 為 `eyeball`（真人訪客而非機器人），且回應碼在 200 到 399 之間，以此來統計真正的 visits 流量。

這裡有兩個統計數字可以拿來參考：
- `sum.visits`：代表不重複訪客的 Visits。
- `count`：代表符合篩選條件的總 Request 次數。

---

## 如何估計 AI 爬蟲（AI Crawler）的流量？

要精準過濾 AI 爬蟲的最直覺方式，是在 GraphQL 查詢中加上 user-agent 的模糊比對：

```graphql
ai: httpRequestsAdaptiveGroups(
  limit: 1
  filter: {
    datetime_geq: $start
    datetime_lt: $end
    requestSource: "eyeball"
    edgeResponseStatus_geq: 200
    edgeResponseStatus_lt: 400
    OR: [
      { userAgent_like: "%GPTBot%" }
      { userAgent_like: "%ChatGPT-User%" }
      { userAgent_like: "%OAI-SearchBot%" }
      { userAgent_like: "%ClaudeBot%" }
      { userAgent_like: "%PerplexityBot%" }
    ]
  }
) {
  count
}
```
這是用來篩選特定 AI crawler 的查詢區塊，透過 `OR` 條件 and `userAgent_like` 模糊比對，把常見 AI 爬蟲的 request 次數撈出來。

要注意的是，這只能撈出特定 User-Agent 的連線次數，並不能代表這些 AI 真的把你的文章拿去做了什麼用途。但對於一個靜態部落格來說，這種統計方式是最輕量且不依賴外部資料庫的方案。

---

## 側邊欄展開：各家 AI 爬蟲數據細分

為了讓側邊欄元件點開時可以展示更詳細的 breakdown，我們可以讓 API 回傳各家爬蟲的細分數據：

```json
{
  "aiRequests": 47,
  "aiCrawlers": [
    { "id": "openai", "name": "OpenAI", "logo": "OpenAI", "requests": 21 },
    { "id": "anthropic", "name": "Claude", "logo": "Claude", "requests": 8 },
    { "id": "perplexity", "name": "Perplexity", "logo": "PPLX", "requests": 6 }
  ]
}
```
這是 Pages Function 回傳給前端的 JSON 格式範例，拆分出不同爬蟲的個數，好讓前端元件可以分別渲染出對應的圖示與數據。

目前我們在 Function 裡內建分析了以下幾組常見的 AI 與搜尋引擎爬蟲：
- **OpenAI**：`GPTBot`、`ChatGPT-User`、`OAI-SearchBot`
- **Claude**：`ClaudeBot`、`anthropic-ai`
- **Perplexity**：`PerplexityBot`
- **Google**：`Google-Extended`
- **Apple**：`Applebot-Extended`
- **Common Crawl**：`CCBot`

---

## 快取控制（Cache-Control）與時間區間限制

Cloudflare GraphQL Analytics API 有嚴格的 Rate limit。為了避免首頁元件頻繁打 API 導致後台額度被燒光，一定要在 Pages Function 加上快取機制：

```http
Cache-Control: public, max-age=300, stale-while-revalidate=300
```
這個 HTTP Header 告訴 Cloudflare Edge 快取這份資料 5 分鐘（300 秒），在快取過期時，會先用舊資料渲染，並在背景自動發送 request 去更新快取。

當訪客在首頁切換「24小時」或「7天」區間時，因為有 `max-age=300`，這些 request 都會直接命中 Edge 快取，大大減輕了後台的負擔。

---

## 本機開發測試與線上部署驗證

在本地端開發時，我們可以使用 wrangler 來模擬 Pages Functions 的運行環境：

```bash
npx wrangler pages dev public
```
使用 wrangler 來啟動本地端的開發伺服器，它會自動讀取同目錄下的靜態檔案與 Pages Functions 做本地聯調。

記得在專案根目錄下建一個 `.dev.vars` 檔案，用來存放本地測試用的 Secrets，千萬不要把它 commit 進 Git 中：

```text
CF_API_TOKEN="..."
CF_ZONE_ID="..."
SITE_METRICS_CACHE_SECONDS="300"
```
這是在本地測試時所建立的環境變數設定檔，讓 wrangler dev 能夠模擬線上的 env context。

部署上線後，我們可以用 `curl` 來檢驗 API 輸出的 JSON 格式是否正常：

```bash
curl https://your-domain/api/site-metrics?range=24h
curl https://your-domain/api/site-metrics?range=7d
```
這是我們部署完成後，用來測試 API endpoint 是否運作正常、格式是否正確的 curl 指令。

如果回傳格式如下，就代表 API proxy 已經順利接通：

```json
{
  "ok": true,
  "range": "7d",
  "label": "7D",
  "visits": 128,
  "requests": 932,
  "aiRequests": 47,
  "aiCrawlers": [
    { "id": "openai", "name": "OpenAI", "logo": "OpenAI", "requests": 21 },
    { "id": "anthropic", "name": "Claude", "logo": "Claude", "requests": 8 },
    { "id": "perplexity", "name": "Perplexity", "logo": "PPLX", "requests": 6 }
  ],
  "source": "cloudflare-graphql",
  "aiDetection": "user-agent"
}
```
這段 JSON 是線上 API 實際吐回的正確資料範例，包含 7 天內（7d）的 visits、requests 與各 AI 爬蟲的抓取統計數據。

如果回傳了 `503`，請去 Pages 後台確認 `CF_API_TOKEN` 有沒有填寫正確；如果是 `502`，則通常是 Zone ID 填錯，或者是 GraphQL 裡設定的時間範圍有問題。

透過這套架構，我們就把靜態網頁（在 Git 版控）與敏感金鑰（留在 Cloudflare 後台）切分得非常乾淨。未來如果需要引入更長期的流量統計，只需要再把 Pages Function 對接 D1 即可，完全不需要去更動原本的前端程式碼架構。

---

## 相關文件參考

- [Cloudflare GraphQL Analytics API 官方文件](https://developers.cloudflare.com/analytics/graphql-api/)
- [設定 Analytics API token 教學](https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/api-token-auth/)
- [Cloudflare Pages Functions 環境變數綁定指南](https://developers.cloudflare.com/pages/functions/bindings/)
- [Cloudflare AI Crawl Control 整合 API 說明](https://developers.cloudflare.com/ai-crawl-control/reference/graphql-api/)
