核心教訓:同 origin 不等於同一個 app

只要用了 client-side router(SPA morph、Turbo、htmx boost 這類),跨 build 的邊界都要顯式排除。否則 router 會拿 A 的骨架去 morph B 的 HTML,morph 出四不像。

我的部落格就是兩個 build 縫在同一個 origin:Quartz 管主站,另一個 Astro build 負責幾個獨立頁面區塊(例如策展頁)。對瀏覽器來說都是同一個網域;對 router 來說,它以為全站都是自己。這條縫是一整類「導航怪象」的根,症狀包括:切到策展頁 navbar 內容不一樣、換頁時畫面跳動、文章頁 console 噴不明錯誤。

兩 build 縫線還有次生代價:navbar、SEO、sitemap 各做各的,需要刻意對齊。Astro 頁不在 Quartz 產的 sitemap.xml 裡,就是同一條縫的 SEO 版本。

根因與修法

三個症狀,其中兩個同源於「Quartz 的 SPA 假設遇到 Astro」,第三個最後證明不是站的 bug。

Quartz 預設開啟 SPA routing:它攔截頁面導航,用 GET 抓新頁 HTML,再用 micromorph 對頁面做 diff、只替換有變的部分,不整頁重新載入;可以在設定裡把 enableSPA 設成 false 關掉(Quartz 文件:SPA Routing)。

問題是 router 攔截到指向 Astro 區塊的連結時,一樣把 Quartz 的 body morph 成 Astro 的 body。兩邊 DOM 結構不同,結果就是 navbar 壞掉。

修:在 router 決定要不要攔截的地方加一道守衛——遇到 Astro 負責的路徑前綴就不攔,交給瀏覽器整頁載入,不 morph。

換頁跳頁

全域 CSS 有 scroll-behavior: smooth,SPA 換頁時那一次 scrollTo(top) 被動畫化,畫面從舊的捲動位置一路滑回頂端。

修:換頁那次的 scrollTo 指定 behavior: "instant",覆蓋 CSS 的 smooth;hash 錨點跳轉保留 smooth。

console 噪音

那些錯誤不在站的原始碼裡,來自第三方腳本(廣告、分析、留言系統)。不是站的 bug。e2e 測試裡用「錯誤來源腳本的網址是不是第一方」來過濾。

怎麼預防

  • 給 client-side router 一份跨 build 路徑前綴的排除清單。每次新增由另一個 build 負責的路徑前綴,同步加進這份清單。
  • 用 e2e 測試當迴歸網:斷言跨 build 連結一定是整頁載入。做法是點連結前在 window 上放一個 sentinel 標記,導航後確認它被清掉——被清掉代表真的整頁重載,沒被 morph。

相關主題