After writing for a while you notice that reading experience is shaped less by the words than by typography: whether code is highlighted, whether warnings get noticed, whether a long post can be skimmed. This sample article explains how the writing components in this theme are designed and used, and doubles as a template for new posts.
Why render at build time#
The most common misconception about static sites is that “static” means “no interaction”. Interaction stays; only its execution point moves.
Anything that can be decided at build time should not be computed in the browser.
In this theme, callouts, file trees, terminal output and diagram shells are emitted as HTML during the build. Only things that genuinely need a browser — mermaid rendering, code copy buttons, the image lightbox — keep a small runtime script.
Anything that can be solved at build time should not be left to runtime.
The benefit is concrete: the first paint is complete HTML, search engines and RSS readers see exactly what a reader sees, and nothing breaks when a script fails to load. The trade-off is that editing content requires a rebuild, which is negligible for a personal blog.
Writing workflow#
Adding a post follows a fixed sequence:
Previewing takes a single command:
Local preview defaults to port 1313; pass -p to use another one if it is taken. Drafts are never included in the build output.
The content tree looks like this — indentation defines hierarchy:
- content
- postsArticles, one directory each
- components-test
- index.mdChinese original
- index.en.mdEnglish translation
- bryce-canyon.jpgPost image
- components-test
- aboutAbout page
- linkLinks page
- postsArticles, one directory each
- data
- link.jsonLinks data
- music.jsonPlayer playlist
- exampleSiteDemo site
Code and version diffs#
Plain code blocks are highlighted by Chroma at build time; no client-side highlighter is loaded:
// Highlighting happens during the build, not in the browser
const build = (source) => source.replace(/\s+$/, '')
export const slugify = (title) =>
title
.toLowerCase()
.replace(/[^\w\u4e00-\u9fa5]+/g, '-')
.replace(/^-|-$/g, '')
When the same idea appears in several stacks, use a code group:
@tab hugo
hugo --minify --gc
@tab bash
hugo server --buildDrafts --navigateToChanged
@tab toml
theme = 'helloblog'
Configuration posts often need to show what changed. A diff block reads better than a screenshot and can be applied directly:
--- a/hugo.toml+++ b/hugo.toml@@ -8,6 +8,7 @@ defaultContentLanguage = 'zh' enableRobotsTXT = true+disableKinds = ['term'] [outputs] home = ['html', 'rss', 'json']A diff block must follow git’s unified diff format: keep the file headers, the @@ hunk header and the leading + - space characters, otherwise the copy cannot be applied.
Structured content#
Processes fit a flow block; arrows are drawn automatically:
For freer topology, switch to mermaid: the build only emits a container and the diagram renders on demand:
graph LR
A[Markdown] --> B[render hook]
B --> C[Static HTML]
C --> D[Browser interaction]Content with a time axis belongs on a timeline, drawn in CSS rather than an image:
For trade-off discussions, side-by-side columns beat a table:
- First screen is HTML
- SEO-friendly
- Content changes require a rebuild
- First screen depends on JS
- SEO needs extra work
- Content changes need no build
Common questions go into a Q&A block; answers stay collapsed so they do not interrupt the reading flow:
QDo these components increase bundle size
QWhy not render with a frontend framework
Long asides can be collapsed:
A collapsible supplement lives here
Typography details#
Inline elements follow the same convention: keys are written as Ctrl + C, versions as v1.0, emphasis uses highlight, and everything else is standard Markdown — bold, italic, inline code.
Lists and quotes behave natively:
- Unordered lists for parallel items
- Ordered lists for steps with a fixed order
- Blockquotes only for quotations, outside any component pipeline
Typography is not about looking good; it is about lowering the cost of understanding.
Tables work for comparisons:
| Component | Rendered at | Depends on |
|---|---|---|
| Callouts / file tree / terminal | Build time | Nothing |
| Diagram shell | Build time | mermaid.js |
| Code highlighting | Build time | Chroma |
| Search | Runtime | minisearch |
Images use page resources, so width and height are filled in at build time to prevent layout shift:

Keep the image next to index.md and reference it with a relative path; a missing file fails the build instead of being skipped silently.
Both external and internal links use plain Markdown; external ones automatically get target="_blank" and rel="noopener noreferrer", for example Hugo documentation
and About this site
.
Wrapping up#
The value of components is not their number but their consistency: one typographic language across the whole post, so readers never have to re-learn the structure. Every component above can be copied into a new article and reused by replacing the text.