核心教訓:同 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。
navbar 被弄壞
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。
相關主題
- Quartz 會把連結的 .html 去掉:同一條 Quartz/Astro 縫線,連結格式那一側
- public:同一條縫線,版控那一側