The first draft of this site had a lot of things: star charts, orbit lines, gradient glows, breathing particles. Only a fraction survived. The real work turned out to be less about making it look cosmic and more about making it quiet, readable, and worth returning to.
Why vite-ssg
A blog is content first and an application second. A pure SPA can look beautiful, but search engines and link previews usually see an empty <div id="app">. I did not want to trade discoverability for motion.
vite-ssg offers a useful middle ground:
- development stays on plain Vite + Vue 3;
- every route is pre-rendered to static HTML at build time;
- article pages can still have a table of contents, reading progress, search, and page transitions;
- the output is a static directory with no Node server required.
It does not hide the architecture the way a larger meta-framework might. Content, routing, and SEO remain visible and editable.
Compile content into data first
Markdown never reaches the browser as Markdown. Before the build starts, a small script walks through content/, parses frontmatter, compiles the body to HTML, and generates the article index, table of contents, reading time, and search data.
const routes = posts.flatMap((post) => [
`/writing/${post.slug}`,
`/en/writing/${post.slug}`,
])
// vite-ssg pre-renders every path in this list
return [...staticRoutes, ...routes]
That gives us two benefits. The archive only needs a small metadata index instead of loading every article body. And the article page receives finished HTML, so the browser needs neither a Markdown parser nor a full syntax-highlighting engine.
Code highlighting happens in Node and leaves behind plain HTML. By the time a reader opens an article, the stars are still drifting, but the code is already sitting quietly on the page.
Bilingual is not a duplicated page
The hard part of a bilingual site is not translation. It is keeping the structure paired: the same article, project, and tag should point at each other in both languages.
Each piece of content has a translationKey. The page looks up the matching version when the language changes. If a translation exists, readers stay on the same article. If it does not, they land on that language's archive instead of a 404.
Language is never chosen automatically by the browser. Chinese lives at the root, English under /en/, and both have their own canonical URL, hreflang, and RSS feed. To a reader it is one click. To a crawler it is two clear, indexable versions.
Performance is part of the design
A deep-space theme makes it tempting to add more effects. But every extra layer, blur, and particle takes a little attention away from reading.
The final rules were:
- one Canvas starfield, with lower density on mobile;
- page transitions no longer than a single breath;
- system fonts for Chinese, self-hosted type only for the Latin display face;
- every decoration stops under
prefers-reduced-motion; - motion never carries information, so turning it off loses nothing.
The first principle of this observatory is not “wow”. It is a place you can stay. The visuals are a door into the content, not a wall in front of it.
One more observation
NOVA is only beginning. More notes on Git, open source, and engineering habits will arrive over time. The pages can keep changing, but the structure that lets writing come first will remain.
The universe is large. A blog is small. Let it shine steadily in the dark for a while.