Web / SEO / Cloudflare

robots.txt、sitemap.xml 都回 200,為什麼網站還是幾乎看不見?

這不是一篇把官方文件重寫一次的教學,而是本站在 Cloudflare Workers Static Assets 上真的發生過的故障:三個重要網址都回 200,但其中兩個其實只是 React 首頁。

· 約 8 分鐘 · 正式環境實測

先講結論:狀態碼 200 只代表伺服器有回應,不代表 robots 是 robots、sitemap 是 XML,也不代表每個 SPA 路徑都有自己的可索引內容。驗收必須同時看 Content-Type、正文、canonical、URL 是否出現在 sitemap,以及頁面是否真的不同。

故障長什麼樣子

本站是 React 單頁應用,部署在 Cloudflare Workers Static Assets。修正前公開環境的結果是:

網址HTTP實際內容
/200 HTMLReact 首頁;原始 HTML 的 #root 是空的
/robots.txt200 text/plainCloudflare 管理內容後面接了 SPA 首頁 HTML
/sitemap.xml200 text/html不是 XML,是完整首頁
/stories/200 HTML仍是首頁,不是內容中心

如果巡檢只檢查「是不是 200」,這四項會全部顯示綠燈。搜尋引擎實際拿到的,卻是一張幾乎只有 JavaScript bundle 的名片。

根因:SPA fallback 把「不存在」包裝成「成功」

Cloudflare 的 Static Assets 可以設定 not_found_handling: "single-page-application"。這對前端路由很方便:找不到實體檔案時回首頁,React 再接手。但 robots、sitemap、內容頁如果沒有真實檔案,也會落進同一條 fallback。

Cloudflare Static Assets 官方文件有列出不同 not-found 行為;真正要記住的是:SPA fallback 是路由便利,不是 SEO 內容生成器。

修法一:部署真的 robots.txt 與 sitemap.xml

兩個檔案必須放在 Static Assets 實際發布的根目錄,而不是只在 React route 裡模擬。本站使用的最小 robots 如下:

User-agent: *
Allow: /

Sitemap: https://drnogreasy.life/sitemap.xml

sitemap 則列出每一個真正能打開、canonical 正確、正文不同的公開頁。Google 的建立 sitemap 官方指南也明確提醒:sitemap 是發現 URL 的提示,不是保證收錄。

修法二:讓首頁原始 HTML 先有可讀內容

Google 能執行 JavaScript,但渲染有第二階段,失敗模式也比靜態 HTML 多。本站沒有為一張品牌首頁整套改寫成 SSR,而是在 #root 裡放一份精簡、可讀、含 H1 與深層連結的 HTML fallback;React 啟動後會完整替換它。

<div id="root">
  <main id="main" class="seo-fallback">
    <h1>一人數位工作室,把複雜的事做到能用</h1>
    <a href="/stories/">文章與專案</a>
    <a href="https://blog.example.com/2026/case-study/">案例全文</a>
  </main>
</div>

這不是 cloaking(對搜尋引擎與人顯示不同內容);fallback 與渲染後頁面表達同一個品牌、專案與文章,只是精簡版。Google 的 JavaScript SEO 官方指南可作為這類網站的基準。

修法三:內容頁要是實體 HTML,連結要真的可爬

我新增 /stories/index.html 與這篇文章自己的 index.html。每頁都有獨立 title、description、canonical、H1、正文和 Article 結構化資料。首頁與內容中心則用普通 <a href> 指向它們,不靠 click handler。

另外把首頁三張文章卡從「全部連部落格首頁」改成各自的文章深層網址。Google 的可檢索連結最佳做法說得很直接:爬蟲能可靠發現的是帶有效 href 的 anchor。

我用什麼判定正式站真的修好

部署完成不是看 CI 綠燈,而是重新從公開網路讀一次:

  1. local commit 與 GitHub main SHA 一致。
  2. 首頁 200,原始 HTML 已有 H1、內容中心連結與至少三個文章深層連結。
  3. /robots.txt 是 text/plain、不含 HTML、允許 search,且宣告 sitemap。
  4. /sitemap.xml 是 application/xml,能被 XML parser 解析。
  5. /stories/ 有自己的 canonical,而且正文雜湊與首頁不同。
  6. React 渲染完成後沒有重複 main/H1,所有外部深層連結實際回 200。

專案也新增 npm run verify:seo,把上述可在建置期確認的項目變成回歸檢查。這比在下一次改版後「記得手動看一下」可靠得多。

Cloudflare managed robots.txt 的額外陷阱

Cloudflare 可能在自訂 robots 前面加上 Managed Content Signals。本站部署後的 robots 從幾十 bytes 變成約 1.9 KB,但它仍是合法純文字,並清楚標示 search=yes。所以「robots 檔案應該很小」不是可靠 gate;應檢查正文是否含 HTML、搜尋爬蟲是否被擋,以及 sitemap 宣告是否仍存在。

可爬之後:用 D1 建立隱私最小化的瀏覽基準

修好搜尋入口後,還有第二個證據缺口:部署與可索引都不能回答「有沒有人載入頁面」。本站在 2026 年 8 月 13 日上線第一方 Worker 端點 /api/analytics/view 與彙總查詢 /api/analytics/summary。瀏覽器只送規範化路徑與頁面語言;Cloudflare D1 每列只保存台灣日期、15 個允許公開路徑之一、語言與累加數字。

事件端點固定只接受 https://drnogreasy.life、拒絕未知路徑,request body(請求內容)超過 512 bytes 就停止,並在寫入 D1 前套用 Rate Limiting binding(速率限制綁定)。Cloudflare 邊緣提供的來源 IP 只和台灣日期做 SHA-256,每個每日輪替雜湊全站最多 10 次/分鐘;原始 IP 與雜湊都不寫入 D1,也不從查詢回傳。Cloudflare 將 D1 定義為 serverless SQL database(無伺服器 SQL 資料庫),並在 Rate Limiting API 文件明確提醒:限制是分節點、寬鬆且近似的,不是精確會計系統。

公開的7 天彙總與30 天彙總免 token,只回傳 aggregate JSON(彙總 JSON),並標示 noindex。這是頁面載入計數器,不是不重複真人;它可以回答哪些公開頁面被載入、週/月 gate 是否移動,但最終仍要用 Cloudflare traffic 與 Search Console 交叉核對 bot、發現、impressions、clicks 與 indexed state(索引狀態)。

這次修完,還不能宣稱什麼

可爬、可解析、已部署,不等於已索引,更不等於排名上升。D1 基準現在能量到匿名頁面載入,但不等於不重複真人;Search Console 的索引、impressions 與 clicks 在取得驗證權限前仍是「未量測/等待」,不能寫成「SEO 已成功」。

如果你也常遇到「push 成功、網址 200,但正式站其實還沒對」的情況,可直接套用AI Agent Production 完成定義,逐層驗證 commit、runtime、HTTP body、UI、監控與 rollback。

你的網站也有「全綠但沒人看」嗎?

我處理 React、Next.js、Cloudflare Workers、Vercel 與跨平台 App 的產品開發,也會把可驗證的部署與搜尋入口一起交付。

到工作室首頁聯絡我,或先看更多真實案例。