這個博客最初的草稿裡有很多東西:星圖、軌道、漸變光暈、會呼吸的粒子。最後保留下來的只有一小部分。真正花時間的地方,反而不是“讓它看起來像宇宙”,而是讓它安靜、可讀、可以長期寫下去。

為什麼是 vite-ssg

博客首先是內容,其次才是應用。純 SPA 能做出漂亮的頁面,但搜索引擎和鏈接預覽看到的往往只有一個空的 <div id="app">。我不想為了動效犧牲文章的可發現性。

vite-ssg 提供了一個合適的中間態:

  • 開發時仍然是熟悉的 Vite + Vue 3;
  • 構建時把每條路由預渲染成靜態 HTML;
  • 文章頁依然可以有目錄、閱讀進度、搜索和頁面轉場;
  • 輸出是一整個靜態目錄,部署時不需要 Node 服務。

它不像完整的元框架那樣包辦一切,反而讓內容結構、路由和 SEO 都能保持透明。

先把內容編譯成數據

Markdown 文件不會直接進入瀏覽器。構建開始前,一個小腳本會掃描 content/,解析 frontmatter,把正文編譯成 HTML,同時生成文章索引、目錄、閱讀時間和搜索數據。

const routes = posts.flatMap((post) => [
  `/writing/${post.slug}`,
  `/en/writing/${post.slug}`,
])

// vite-ssg 會把這些路徑全部預渲染
return [...staticRoutes, ...routes]

這樣帶來兩個好處。第一,列表頁只需要一小組元數據,不必加載所有文章正文。第二,文章頁拿到的已經是安全的 HTML,瀏覽器端不需要 Markdown 解析器,也不需要為代碼高亮加載一整套語法引擎。

代碼高亮在 Node 端完成,最終輸出純 HTML。讀者打開文章時,星野還在慢慢移動,但代碼已經安靜地躺在那裡了。

雙語不是複製一份頁面

中英雙語最難的部分不是翻譯文案,而是保持結構一致:同一篇文章、同一個項目、同一個標籤,在兩種語言裡都指向彼此。

我給每篇內容加了一個 translationKey。頁面根據當前語言和鍵值尋找對應版本。切換語言時,如果譯文存在,就停在同一篇文章;如果不存在,就回到該語言的歸檔頁,而不是把用戶丟進 404。

路由也不使用瀏覽器自動判斷。中文在根路徑,英文在 /en/,兩邊都有獨立的 canonical、hreflang 和 RSS。對讀者來說只是點一下切換,對搜索引擎來說則是兩套清晰、可索引的頁面。

性能也是設計的一部分

深空主題很容易讓人想加更多效果。但每多一層背景、多一個模糊、多一點粒子,閱讀時的注意力都會被分走一點。

最後的選擇是:

  • 星野只使用一個 Canvas,移動端降低密度;
  • 頁面轉場控制在一次呼吸的長度;
  • 中文字體使用系統字體,英文標題才加載自託管字庫;
  • 所有裝飾在 prefers-reduced-motion 下停止;
  • 動效不承載信息,關閉它也不會損失內容。

我希望這座觀測站的第一原則不是“炫”,而是待得住。視覺是進入內容的門,不是擋住內容的牆。

留給下一次觀測

現在的 NOVA 還只是一個開始。之後會慢慢加入更多關於 Git、開源和工程習慣的記錄。頁面可以繼續變化,但那套讓內容先被讀到的結構會留下來。

宇宙很大,博客很小。先讓它在黑暗裡穩定發光。