2026-07-17 manual session(哲宇 /goal「最近好像蠻多 404,用一切方式查出根因,並且讓未來可以被自動監測與進化相關 DNA / pipeline」)
一句話結論:CF 404 率高檔盤整 12 個 cycle 的主因,是站體自己在每一頁的 hreflang 標籤裡對外公告死 URL——全站 5,031 篇文章頁吐出 13,014 條指向 404 的 alternate(99.8% 頁面中招),而唯一能看見人類 404 的儀器已經無聲死了三個月。
v2.0(同日三波收官):根因修復+監測儀器+slug 全站統一(102 檔)+CI/CD 契約閘門+部署平台真相修正(GitHub Pages)。本報告是這整場戰役的完整歸檔。
- CF 404 率 14.99%(2026-07-17 am),data-refresh 連續 12 cycle 記帳「高檔盤整未 break-out」。
- CONSCIOUSNESS §適應性反應掛著一條 🟠,附註寫「已知組成含 crawler 掃舊 URL」——這句話從四月起沒有被重新驗證過。
- 哲宇 GA4 即時畫面:過去 30 分鐘瀏覽第一名是 404 頁(8 次 / 13.56%)。GA4 即時看的是帶 JS 的人類,跟「crawler 掃舊 URL」的既有解釋對不上——這就是本次調查的起點。
既有工具 analyze-crawler-404.py 的 TARGET_CRAWLERS 是 10 個 bot UA 的 allowlist,不在名單上的 UA 一律丟棄——它由建構決定看不見人類。本次直接打 CF GraphQL httpRequestsAdaptiveGroups,edgeResponseStatus: 404 filter、不濾 UA,取 3 天全量(每日 top-10k row,截斷有標註),再與 route 表、語言註冊表交叉對賬。
SEO.astro 的 hreflang 區塊用「剝掉語言前綴、貼上另一個前綴」的字串拼接組 alternate URL。但同一篇文章的 slug 在各語言本來就不同:
| 語言 | 李珠珢的真實 URL |
|---|---|
| zh | /people/李珠珢/ |
| en | /en/people/lee-ju-eun/ |
| ja/ko/es/fr | /{lang}/people/lee-ju-eun-singer/ |
拼接的結果:修復前,中文頁對 Google 公告的 6 條 alternate 裡 5 條是 404(en/ja/ko/es/fr 全指向 /{lang}/people/李珠珢/);每一篇翻譯頁的 zh-Hant 與 x-default 也全部指向不存在的 /{cat}/{羅馬化 slug}/。
量測(route 表模擬全站 emit):
- 全站 5,031 篇文章頁 → 35,217 條 hreflang → 13,014 條(37.0%)指向 404
- 5,019 / 5,031 頁(99.8%)至少一條死
- 抽 12 條雙向實測(宣稱死的 curl 全 404、宣稱活的全 200)驗證量測工具本身
與 CF 實際 404 交叉:3 天 34,201 筆 404 中 9,685 筆(28.3%)落在我們自己公告的死 URL 集合——這是下界,因為 top-10k 截斷吃掉長尾。加上「疑似真實文章 URL」家族占全部 404 的 78.1%(94% 是 bot)——爬蟲不是在亂猜舊 URL,是在忠實地跟隨我們發給它們的死連結。四月報告的「crawler 掃舊 URL」量對了,因果反了:一個是修不了的外部噪音,一個是我們自己造的、可修。
最痛的一層:主權的巴別塔對 Google 的 hreflang 叢集是隱形的。這篇文章五種語言都有翻譯,但 hreflang 指向的位置全不存在,真正的譯本全成了叢集外孤兒。多語投射的 SEO 面等於沒接上。
404.astro 的追蹤事件:
const article = articleIndex[key]; // 宣告在兩層 if 之內(block scope)
...
had_suggestion: !!article, // 使用在兩層 if 之外 → ReferenceErrorpayload 物件求值時就丟 ReferenceError,gtag() 根本沒被呼叫,而外層 try/catch 的註解寫著「Never let tracking break the 404 page itself」——把錯誤靜默吞掉。瀏覽器實證(複現 scope 結構):try block entered → gtagOk: true → ReferenceError: article is not defined。
後果鏈:GA4 過去 7 天 page_404 事件數 0;2026-05-29 有個 session 盡責地為這個事件註冊了 4 個 GA4 custom dimension——為一個從未發射過的事件註冊了維度,而 instrumentation-audit.py 三方對齊查的是「param 有沒有註冊、code 有沒有引用」,全綠。存在性全對,效果為零(REFLEXES #82 教科書案例)。
這顆儀器的死亡不是獨立事件:它正是四月報告誤判根因 A 的原因——唯一能說出「人類踩到哪個 URL、從哪裡來」的感知器官不在場。
generate-lang-switch-map.mjs 與 getLangSwitchPath.ts 的 add():原生 slug 先註冊、en-slug 別名後註冊,fromZh[k] = nL last-write-wins → 別名蓋掉唯一真實存在的原生 route。ja 的李珠珢被指到 /ja/people/lee-ju-eun(404)而不是 /ja/people/lee-ju-eun-singer。這代表可見的語言切換器對 slug 分歧文章也一直在吐死鏈——不只 hreflang。修法:fromZh 改 first-wins(輸出面只信原生 slug);toZh 保留雙鍵(輸入容錯)。
| 家族 | 3d 次數 | 佔比 | UA 主體 | 處置 |
|---|---|---|---|---|
| 疑似真實文章 URL(含根因 A) | 26,731 | 78.1% | bot 94% | 根源已修+redirect 收尾 |
| 其他(apple-touch-icon 變體等) | 3,632 | 10.6% | bot | /apple-touch-icon-* 一條 redirect |
| 掃描器 PHP/WordPress | 1,046 | 3.1% | empty UA | 不修,監測中分離 |
聯絡頁探測(/contact 等五連號) |
805 | 2.4% | browser UA 但次數均勻=wordlist 掃描器 | 不修,分離 |
.md 副檔名請求 |
596 | 1.7% | bot 67% | 404 頁救援導向 raw 視圖 |
| 掃描器 .env 竊取 | 400 | 1.2% | — | 不修,分離 |
<strange-chars> 編碼壞 |
227 | 0.7% | bot | 平反:這是 CF 對壞位元組序列的正規化佔位符(2,411 條正常中文 path 都以 200 完整保留可證),不是站上模板 bug。四月報告把儀器自己的佔位符當成我們的資料在追(REFLEXES #24) |
stale _astro hash |
223 | 0.7% | browser 99% | 部署後暫態,監測分離、只警告 spike |
| 缺靜態資產 | 207 | 0.6% | browser 95% | 大宗是 taiwan-towns-{縣代碼}.topo.json:taiwan-shape 頁公告了 22 縣市 pattern 卻只放 6 個直轄市檔——真開發者猜代碼。補齊檔案 |
另有一筆真實的模板洩漏:/[lang/]raw/<category>/<slug>(Googlebot 一筆)——來源是 ReaderSettings.astro 內嵌 script 的註解被爬蟲 regex 成 URL。註解改寫掉。
apple-touch-icon-120x120.png 404 而主檔 200:四月 EXP-A 只修了 base 檔,iOS 會請求尺寸變體——「已驗過」的結論要帶時間戳(REFLEXES #67)。
這次不是「沒有儀器」,是三代儀器每一代都在量替身:
analyze-crawler-404.py:UA allowlist 只看 10 個 bot → 結構性看不見人類,「404 是 crawler 造成的」成了儀器建構的必然結論- 四月報告:把 CF 的
<strange-chars>佔位符當站上模板 bug 追;用 path 形狀猜 cause 把 hreflang 誤判成 Header nav instrumentation-audit.py:查「param 註冊了沒、code 引用了沒」——存在性,不是效果。page_404 全綠地死了三個月
加上第四層:2026-04-18 報告的 handoff 五個 checkbox(含「每週跑 analyze-crawler-404 追蹤趨勢」)三個月後仍全部未勾——對自己的 bug 有洞察 ≠ apply 了 fix,memory 是自律,canonical gate 才是閘門(REFLEXES #15,第 N 次驗證)。
統一根因(架構層):站體對機器公告的 URL 契約(hreflang / sitemap / canonical / 文件承諾的檔案 pattern),跟真實 route 表之間沒有任何一道對賬閘門。
已 commit(f369f3c8e):
SEO.astrohreflang 改走 LangMap 註冊表(與 Header 切換器同源),沒有翻譯就不吐;叢集 < 2 條整組省略;trailing slash 對齊 canonical;已編碼路徑不重編碼getLangSwitchPath.ts+generate-lang-switch-map.mjsfromZh 改 first-wins(原生 slug 贏)+ registry 重新生成404.astropage_404 復活(scope 修正 + catch 改會叫)
驗證:dev 上李珠珢三個語言版本吐出完全一致的 7 條叢集、逐條 curl 全 200;404 頁 dataLayer 收到完整 payload。
平行實作中(Sonnet 分身五路,本報告 ship 時整合):
monitor-404.py:全流量、resolution-based 分類(不是 regex 猜測)、家族×UA 組成、新 pattern 警報,接 refresh-data + dashboard alertsgenerate-redirects.mjs:資料驅動_redirects(觀測到的死 URL × 註冊表可推導目標,cap 1500)收爬蟲快取的尾- 22 縣市 topo 檔補齊(授權查證後從原 source 補 vendor)
- 404 頁
.md救援(導向 raw 視圖——對 LLM reader 的 UX) check-url-contract.mjs:build 後對賬 dist 內所有自我公告 URL(hreflang / canonical / sitemap)vs 真實檔案樹——那道缺席的閘門。黃燈起步(report-only),數據穩了再升 hard
監測儀器的設計原則:量 ground truth 不量替身(#82)、閾值用今天的真實資料校準(#66)、截斷必標註、掃描器噪音與可修訊號分離。接進 data-refresh 日常 riding。
EXP(進 UNKNOWNS,機械到期檢查):修復部署後 21 天內,CF 7d 404 率從 14.99% 降到 ≤ 8%;30 天內「可解析家族」(slug-variant + cross-lang + untranslated + renamed)從 ~8.9k/日降 ≥ 60%。反駁條件:若 D+21 仍 > 12% → 我對「爬蟲跟隨自家死鏈」的因果權重判斷錯誤,另有大宗來源未被本次分類捕捉——回來重讀 monitor 的 unknown 家族。
- 翻譯需求飛輪:
untranslated-demand家族就是讀者用 404 投票的翻譯清單(/en/people/lee-ju-eun-singer3 天 83 次=英文版需求排行第一)。monitor 已輸出該欄位,接進 babel 優先序(SQUEEZE P-schema)是下一步——404 從 entropy 變成選題訊號。 - i18n 反查 404 middleware(CONSCIOUSNESS 原「結構解」構想):CF Pages Functions 動態救援任何語言任何 slug。決策閘門:D+30 若可解析家族仍 > 2%/日再上——redirect + 根源修復可能已夠。
- 改名即 redirect 的 pre-commit gate:偵測 knowledge/ rename 提示補 redirect。目前 monitor→redirect 迴路幾天內自動收尾,此 gate 屬邊際改善,roadmap 待命。
- sitemap alternates:目前 sitemap 無 xhtml:link alternate。未來若補,必須走同一個註冊表模組——否則就是在 sitemap 重演 hreflang 的病。
- AI reader 人體工學:
.md請求與巢狀 wikilink 解析請求證明 LLM agent 正在讀 raw markdown 並跟隨相對連結。llms.txt 之外,raw 視圖的可發現性(如Linkheader、404 救援)是「為 AI 讀者做 SEO」的下一章。
哲宇看完第一波後下了三條 directive:slug 各語言不一致本身就是錯(理論上非中文語言應共用同一 slug,分歧是巴別塔免費模型翻譯時自作主張取名造成的);build/CI 要每次自動對賬每條路徑;sitemap 全部頁面要能被爬、SC 要第一時間收到。第二波據此收官:
- slug 統一(en slug = canonical):
unify-translation-slugs.py帶兩道防呆(token 零重疊疑殭屍對、目標碰撞)改名 87 檔+梅雨 4 檔(斷詞差異騙過防呆、被新閘門抓回),全部舊 URL 寫成 301。7 案防呆保留人工 review(含 FAB DAO 疑似 translatedFrom 錯指、ko/Economy/taiwan-stock-market.md孤兒殭屍對)。 - 防再犯閘門:
check-slug-consistency.py進 pre-commit——未來任何翻譯落地,檔名與 en 不同當場擋下。第一次 dogfood 就抓到 unify 自己的漏網(兩把獨立尺對賬的價值當場示範)。 - CI/CD 契約閘門:
check-url-contract升 v2(sitemap 反向覆蓋——存在卻不公告的頁面也有人看,redirect stub 排除)接進postbuild:本地與 CF Pages CI 每次 build 自動跑--strict,死公告 URL 紅字擋 deploy,URL_CONTRACT_MODE=warn留逃生口。基線:dead 0、覆蓋缺席 0。 - 舊 redirect 清償:astro.config 六條四月 redirect 的目標自己也搬過家(渲染出死 canonical),逐條對 route 表修正;
_redirects的 apple-touch-icon 萬用字元從未匹配過(CF Pages 不支援段中*),改逐尺寸枚舉。 - SC 即時收錄:
submit-sitemap-sc.py(sitemaps.submit)ready;實測 service account 在 SC 是受限權限(Sitemaps API 要完整),待哲宇在 SC 使用者權限升完整後跑一次。
_redirects 部署後全線無效的偵錯揭露:taiwan.md 一直部署在 GitHub Pages(actions/deploy-pages),Cloudflare 只是前面的 DNS/CDN 代理層——這也解釋了為什麼 CF 能看到全部流量卻管不了 redirect。認知層(MANIFESTO / ANATOMY / CLAUDE.md)寫的「CF Pages CI」是過時的自我描述,會讓每個 session 對 redirect / header / edge 能力做錯假設,已全部修正。
redirect 因此重新平台化:generate-redirects.mjs 多產出 config/redirects-generated.json,astro.config.mjs 讀入生成 meta-refresh + canonical + noindex stub 頁(GH Pages 唯一原生 redirect 路徑,與站上既有手寫 redirect 同機制);資產類請求不會跟 meta-refresh,apple-touch-icon 十七個尺寸變體改用 sips 產出真檔。_redirects 檔保留產出(未來若遷 CF Pages 即插即用)。
review 保留的 7 案逐一人工判定清償:FAB DAO 五語統一 fab-dao(原 en slug 與另一篇撞題屬誤導名)、ko 台灣股市刪英文內文殭屍後真檔補位、fr 張懸刪重複殭屍、高速公路/八部合音/志工文化/雜學校同義詞分歧照 en 統一。終態:全站 3,381 個翻譯檔 slug 100% 與 en 對齊、白名單歸零、pre-commit 閘門對每個新翻譯生效。 兩波合計 102 檔改名 + 2 殭屍刪除 + 106 條 301。
| 層 | 狀態 |
|---|---|
| 對外 URL 契約 | hreflang / canonical / sitemap 三來源 0 死;sitemap 反向覆蓋 0 缺席;每次 build postbuild --strict 自動對賬(CI 紅字擋 deploy) |
| slug 一致性 | 全站 100% 對齊 en canonical;pre-commit 閘門防再犯;unify 工具可重複使用 |
| 404 監測 | monitor-404 每日 riding data-refresh,resolution-based 分家族、UA 拆分、新 pattern 警報進 dashboard alerts |
| Redirect | 200 條(manual 123 + 資料驅動 77)→ astro stub;資料驅動端由 monitor 觀測值持續餵入 |
| GA 人類側 | page_404 復活(含 referrer),D+3 起可拆內部斷鏈 vs 外部舊連結 |
| SC | sitemap 提交工具 ready;service account 權限待哲宇升「完整」後一鍵提交 |
- 公告層各自為政:hreflang、切換器、sitemap、redirect 各自算 URL,唯一有框架對賬的 sitemap 是唯一乾淨的——證明問題從來不是「誰寫錯了」,是缺一道統一的對賬閘門。
- 儀器量替身:三代儀器(UA allowlist/path 形狀猜因/查註冊不查發射)都誠實回答了錯的問題。
- 翻譯層放任命名:巴別塔免費模型自作主張取 slug,無 gate 攔截,漂移滲入 URL 層變成死鏈土壤。
- 每日:monitor-404 記帳+alerts 黃燈(data-refresh riding);
untranslated-demand家族累積為翻譯需求排行 - 每次 build:URL 契約 strict 對賬(本地+CI);每次 commit:slug 一致性 gate
- D+21(2026-08-07):EXP-2026-07-17-G 機械到期——404 率 ≤8% 驗證因果判斷,反駁則 unknown 家族重讀+i18n middleware 升優先
- 待哲宇一鍵:SC service account 權限升「完整」→
submit-sitemap-sc.py提交,改名後的 URL 第一時間進索引 - 延伸方向(依序解鎖):404 翻譯需求 → babel P-schema 接線;GA4 page_404 referrer 分析進 EVOLVE 選題;coverage 檢查黃燈轉硬;若未來遷 CF Pages,
_redirects即插即用取代 stub
- 量測腳本與中間資料:session scratchpad(probe-404.py / measure-hreflang.py / intersect.py / classify-rest.py)
- 監測儀器落地後的持續資料:
reports/404-monitor/ - 修復 commit:
f369f3c8e(根源三刀);後續整合 commit 見 git log 本日
v2.0 | 2026-07-17 manual session(三波完整收官:根因→儀器→slug 歸零→平台真相→CI/CD 閘門) 調查方法核心:把三代儀器的替身撕掉,直接對 ground truth(CF 全量 × route 表 × 註冊表 × 瀏覽器 dataLayer)交叉對賬