這個博客最初的草稿裡有很多東西:星圖、軌道、漸變光暈、會呼吸的粒子。最後保留下來的只有一小部分。真正花時間的地方,反而不是“讓它看起來像宇宙”,而是讓它安靜、可讀、可以長期寫下去。
為什麼是 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、開源和工程習慣的記錄。頁面可以繼續變化,但那套讓內容先被讀到的結構會留下來。
宇宙很大,博客很小。先讓它在黑暗裡穩定發光。