搬家記:把部落格從一個沒人找得到的網域搬回來

舊站的每一篇文章,原始碼裡都沒有 title、沒有 description、沒有 canonical,公式還是純文字。這篇記錄我們把部落格搬回 jecision.com 的過程,以及三個差點讓上線失敗的意外。

4 分鐘Read in English
nuxtseo重構
從臨時網域搬回品牌網域的示意圖

起因是一行原始碼

前陣子要把一篇舊文章轉給同事看,順手抓了頁面原始碼,然後愣住。

<html> 沒有 lang。沒有 <title>。沒有 description,沒有 og,沒有 canonical,沒有一行 JSON-LD。公式的部分更妙——原始碼裡躺著的是 \operatorname{Attention} 這串字,要等瀏覽器把 JavaScript 跑完,人才看得到排版好的數學。

也就是說:對搜尋引擎來說,那篇文章從來沒有存在過。

文章寫得再好,放在一個沒有標題、沒有摘要、沒有結構化資料的頁面上,它就只是一段沒有人索引的文字。

當初為什麼會變這樣

不是不用心,是工具用得比它設計的方式更用力。

當時為了讓公式能顯示,我們在 Markdown 裡塞了 <div class="latex-block">,然後在 onMounted 裡用 KaTeX 把它的 textContent 換成公式。瀏覽器上看起來完全正常,所以也沒有人覺得有問題。

問題在於這個做法把兩件事綁在一起:內容要靠 JavaScript 才成立,而 SEO 需要的是「不跑 JavaScript 也成立」。

搬家的三個決定

一、內容搬家,不是版面搬家。 12 篇文章全部重新轉換,公式改寫成 $...$ 與 $$...$$,交給 remark-math 與 rehype-katex 在 build 時處理。現在公式是 HTML 的一部分,不需要任何前端 JavaScript。

二、網址由資料決定,不由檔名決定。 舊站的檔名是 2604.04921v1.md,結果網址變成 /en/blog/04921v1——數字前綴被吃掉,檔名和網址永遠對不起來。新站改成 frontmatter 裡寫 slug,檔名再亂都不影響網址。

三、舊網址一條都不能斷。 已經被 Google 記住的東西,不該因為我們重寫而消失。28 條 301 對照全部列出來,包含尾斜線的版本。

三個差點讓上線失敗的意外

第一個:slug 被吃掉。 這件事是在舊站發現的——2604.04921v1.md 變成 /en/blog/04921v1。如果沒有先抓一次線上頁面,我們會以為搬家只是複製檔案。

第二個:hreflang 指到不存在的頁面。 新站預設會幫每個語系產生 alternate 連結,但只有中文版的文章,英文連結就是一個 404。跑 nuxt generate 的時候 prerender 直接掛掉,錯誤訊息長得像「Article not found,linked from /blog/xxx」。

解法不是關掉檢查,而是讓每一頁自己宣告「我實際有哪些語系版本」。只有真的存在的中英版本,才會出現在 hreflang 裡。

第三個:/sitemap.xml 是 HTML。 這件事最陰險。build 成功、檔案存在、打開也有內容——但它是一個 <meta http-equiv="refresh"> 的轉址頁,不是 XML。Google 會直接判定 sitemap 無效。

後來在 build 後加了一步,把 sitemap index 的內容直接寫成 /sitemap.xml,才真的能提交。

現在的樣子

  • 公式在伺服器端就排版完成,原始碼裡有 MathML
  • 每一頁都有 title、description、canonical、hreflang、og,文章另有 BlogPosting 結構化資料
  • RSS、llms.txt、sitemap、301 對照都有
  • 還寫了一支 audit 腳本,build 完直接檢查 25 個頁面的 SEO 與壞連結。目前是 0 錯誤 0 警告

一句話總結

搬家最花時間的不是搬,是確認搬過去的東西真的還在。

我們的做法是:把「應該要有」的東西寫成檢查,讓它每次 build 都自己驗一遍。人會忘記,腳本不會。