這次我把民宿官網上靜態的服務介紹,變成真正可以點進去的頁面——BBQ 與生日方案,中英文各一份——並把首頁的服務卡片改成可點擊。功能本身很平凡,真正值得記下來的是之後的三個 bug,因為它們各自代表一類問題:只要你長期維護平行的 zh/en 內容樹,這些坑會反覆出現。
搞掛建置的撇號
英文 BBQ 頁有一個區塊叫「Ocean’s Best」。那個撇號把整個建置弄垮了。
Astro 元件是用接近 JSX 的方式解析的,依撇號落在哪裡(表達式內、屬性裡、還是 fragment 邊界),parser 可能把它誤判成分隔符。我這次撇號剛好離某些內嵌表達式夠近,tokenizer 就亂掉了,丟出的錯誤位置還指在離真正字元好幾個節點外的行——這才是最煩的:錯誤位置會騙你。
修法很無聊(Ocean's 或彎引號 ’),但教訓是結構性的:英文內容有中文沒有的排版字元。 我的 zh 頁從沒踩到這個,因為它沒有撇號、沒有縮寫、沒有 &。所以同一份「內容」,zh 過了、它的 en 雙胞胎掛了。當你跨語系鏡像頁面,會嚇到你的永遠是 en 那邊——撇號、文案裡的 &、從文件貼來的破折號。
現在我把 .astro 裡的英文原文當作不可信輸入。任何含縮寫或 & 的都做 HTML escape,或丟進 frontmatter 字串裡——在那它只是 JS 字串字面值,模板 parser 永遠碰不到。
孤兒 class 稽核
我有個小檢查,會標出在標記裡被引用卻從沒定義的 CSS class(以及反向)。新的 BBQ 與生日頁——四個語系檔——全部沒過。我從既有頁面複製了 class 名稱,但對應規則沒搬過來(或被改名了)。
這是每個語系複製一份頁面的必然代價。內容不同但樣式應該一致,所以大家複製標記,class 與樣式的配對就悄悄飄移。稽核讓這種飄移變得吵,而不是讓一個半套樣式的頁面上線。補上 class 是兩分鐘的事;價值在於稽核有跑。如果你為 N 個語系維護 N 份版面,建置時檢查標記與 CSS 一致是不可妥協的——因為跨 2×M 頁做視覺 QA 正是注意力耗盡的地方。
滲進 hero 的浮水印
最後一個,也是唯一被標 security 的(鬆散地說——是視覺洩漏,不是漏洞)。我的浮水印是用 ::after 偽元素做的。多數頁面它待在該待的地方。但在深色背景的 hero 區塊,它溢出容器、滲過整張圖。
根因是常見的偽元素陷阱:::after 是相對於某個祖先定位的,而那個祖先並沒有建立我以為的 containing block,加上對的元素上又沒有 overflow 邊界,於是它畫到外面去了。只有在深色 hero 背景才看得出來,因為浮水印顏色在那裡才有足夠對比——在淺色頁它就是沒人注意的隱形雜訊。所以它大概早就壞了,只是底下內容(深色 hero)變了才浮現。
我選擇移除那個會溢出的 ::after,而不是修它的定位。一個裝飾性浮水印不值得一個參與每頁堆疊與溢出行為的全域偽元素。「只是 CSS,留著吧」的代價,就是它會跟你之後加的每個 hero 互動。
真正的功能
這一切底下:首頁和訂房頁做了一輪轉換率優化。服務卡片現在會連到對應頁面,而不是死文字;RoomCard 和 Footer 的聯絡 CTA 收緊了;一間房的內容檔也更新了文案——全都 zh/en 同步。沒什麼花招,但同步就是紀律:每個改動都在同一個 commit 裡動到兩個語系檔,這是唯一能讓孤兒 class 稽核和撇號規則維持可控的方式。
複製式語系站的後設教訓:英文內容是建置最脆弱的那份、CSS 飄移在稽核前是隱形的、而裝飾性 CSS 在內容一變就成了負債。