SwufeFox is a student-led open-source community at Southwestern University of Finance and Economics (SWUFE). We build software, research markets, and open-source what we learn. This blog is our front door, and this is the first time we have written down how we build our own tools.

The first thing I remember about mizore's blog was not a particular post. It was the way reading had been turned into an interface: paper, hairline rules, a dark title block, like a dossier that had been opened many times. We decided to rebuild it with Next.js and ak-ui, not to make a prettier copy, but to turn it into a template that could be maintained and handed to someone else.

This note records the decisions behind that template, and the things we chose not to build.

Content before chrome

There is no database and no admin panel. Every post is a Markdown file under content/posts/, and the filename is the slug. At build time, gray-matter reads the front matter and unified handles the rest:

yaml
---
title: Building a dossier terminal for notes
date: 2026-11-12
category: Meta
tags: [Next.js, ak-ui]
---

title, description and a YYYY-MM-DD date are required; tags must be a list; category is optional. If one of them is wrong, next build stops and names the offending file. For a personal blog, that is more trustworthy than silently skipping a post at runtime.

FIG 01One build, one path
flowchart LR
  A[Markdown] --> B[front matter + validation]
  B --> C[unified / remark / rehype]
  C --> D[HTML + contents]
  D --> E[next build]
  E --> F[pages / RSS / sitemap]

Content settles at build time; the browser only takes over interaction.

The build pipeline: Markdown to AST to HTML

Images, syntax highlighting, math, data tables, Mermaid, hand-written SVG and ECharts all run through the same pipeline. Local images get intrinsic dimensions and loading="lazy", so the prose does not jump when a file finishes downloading.

Two languages, one address

Chinese lives at the root; English lives under /en. A matching file in content/posts/en/ becomes the translation automatically. Without one, the post simply exists in Chinese only.

The template defaults to a Node runtime. This blog is deployed to Cloudflare Pages, so it switches to a static export during the build: next build writes /zh/... and /en/..., then the /zh output is copied to the root and /zh is redirected there by _redirects; content/images is copied into /content/images. There is no proxy, image route or dynamic 404. Pages, RSS, sitemap and the social card are still build-time output.

A design language, not a skin

The template uses ak-ui at accent intensity: it keeps the reading layout and borrows typography, signal colours, compact metadata and one restrained cut. Black, white and grey carry structure. Action yellow appears only on real state — the current navigation item, tag signals, search hits, reading progress and the current contents section. Danger red is reserved for the error page. SwufeFox navy, gold and the fox mark live only in the brand layer: the hero, the header mark and the social card; they never replace ak-ui's semantic colours.

The home page has one dark focal point: a navy hero, with SWUFEFOX and the community description on the left, the fox mark on the right, and a 24px cut at the bottom-right corner. Everything else is flat: no floating shadows, no row of identical rounded cards, no invented numbers pretending to be telemetry.

FIG 02Four decisions
ChoiceResult
Build-time outputPages, RSS and sitemap are generated by next build
Chinese at the rootEnglish under /en; matching Markdown files are translations
Navy hero + fox markThe whole site has a single visual focus
Ink structureAction yellow is reserved for state and feedback

Settle the content, then the interface.

The article page turns its contents into a flat rail: no panel fill or border, only an ink rule on the left and a yellow marker for the current section. The first paragraph is slightly larger, as an entrance into the piece. The archive uses AkTable instead: type, name and post count. Three columns are enough; it does not need another pair of oversized section headings.

The order of interaction

What makes a site worth returning to is usually not animation. It is whether the state is honest.

  • The search query lives in ?q=, so a refresh, a shared link or a return from another page restores it without flooding browser history.
  • The contents highlight is calculated from scroll position, not from a heading happening to enter a narrow observer band. It never goes blank between two sections.
  • The theme control renders both icon and label states and switches them with [data-ak-theme], so the first frame cannot show the wrong side. theme-color follows it.
  • Copying, Mermaid and ECharts are loaded on demand. Articles without a diagram never download those libraries.
  • Theme, navigation and the mobile menu keep 44px targets; secondary links keep at least 24px. Keyboard focus is never clipped.

The social card speaks the same language: /og returns a 1200×630 composition with a navy field, a cream fox panel and the large SWUFEFOX title. Every page shares it. Chinese stays in the page and description because the default image font only covers Latin.

What we deliberately left out

ak-ui also ships AkScreen, AkCommandGrid, operator cards, stage and rarity components, particles, depth and scanlines. They are excellent for a component catalogue. They are not right for a place where people read.

The template does not use them. There is no fake telemetry, no compulsion to clip, number or uppercase every element, and no trade of reading comfort for the look of a game. Knowing where the design system stops matters more than putting every component on the page.

Closing

The template now has build-time content validation, bilingual static routes, shareable search, continuous contents highlighting, light and dark themes, and a social card of its own. It is still a blog, not a console.

This post is the acceptance test: it goes through the same front matter, image and Markdown pipeline as every post that follows. If it can sit here quietly, the template is ready.

The final variable is yours to change.

— Magixyne, 2026-11-12

Source
content/posts/en/how-this-blog-was-built.md
Category
Meta