A modern static blog built with Astro. Markdown/Markdoc content, LaTeX math, full-text search, dark mode, RSS, multi-language support, Keystatic admin UI, and a settings panel for personalized reading.
๐ Live Demo: blog.qwara.top
- ๐ Markdown / Markdoc โ Write posts in Markdown or Markdoc with Frontmatter
- ๐ LaTeX Math โ KaTeX integration for inline and display math
- ๐ Multi-language โ UI and content i18n (en / zh-CN / zh-TW / ja), toggleable
- ๐ Full-text Search โ Build-time search index, client-side Fuse.js instant search (lazy-loaded)
- ๐ Dark Mode โ Light / Dark / System three modes
- โ๏ธ Settings Panel โ Font size, content width, font family, TOC visibility, language
- ๐ Table of Contents โ Sidebar TOC with scroll-spy highlighting
- โฑ Reading Time โ Auto-estimate for mixed CJK + English content
- ๐ท Tag System โ Tag categorization and tag cloud with pagination
- ๐ก RSS Feed โ Per-locale auto-generated RSS
- ๐บ Sitemap โ Auto-generated sitemap
- ๐ฑ Responsive โ Desktop and mobile friendly
- ๐ View Transitions โ Built-in page transition animations
- ๐ Keystatic Admin โ Local admin UI for content management
# 1. Clone
git clone https://github.com/Qwara-chan/TerseBlog.git
cd TerseBlog
# 2. Install
npm install
# 3. Dev server
npm run dev
# 4. Build
npm run build
# 5. Preview build
npm run preview
# 6. Type check
npm run check
# 7. Run tests
npm testi18n is controlled via src/config/settings.json:
{
"i18nEnabled": true,
"defaultLocale": "zh-CN"
}Toggle at any time:
npm run i18n # View current status
npm run i18n:on # Enable i18n
npm run i18n:off # Disable i18n (single-locale mode)When enabled, 4 locales are served under URL prefixes: /en/ /zh-CN/ /zh-TW/ /ja/. The root path / redirects to /{defaultLocale}/.
When disabled, the build emits a single-locale site: no locale-prefixed routes, no per-locale RSS/search-index, no i18n middleware.
The toggle script physically moves the inactive page tree out of src/pages/ into src/pages/_i18n_disabled/, so Astro only builds the active tree.
Post translations live in src/posts/<locale>/<slug>/index.mdoc. If a translation is missing for a given locale, it falls back to the default locale (zh-CN).
UI text (search, settings, pagination, etc.) is translated via dictionary files in src/i18n/dictionaries/. The runtime is loaded only when a locale change happens (lazy import('@/i18n/runtime')).
See docs/configuration.md for details.
The project includes Keystatic for local content management. Build without it in CI:
npm run build # SKIP_KEYSTATIC=true by defaultTo enable the admin UI, build without the skip flag:
SKIP_KEYSTATIC=false npm run buildAdmin config lives in keystatic.config.ts โ defines per-locale post collections and site settings singleton.
TerseBlog/
โโโ astro.config.ts
โโโ keystatic.config.ts # Keystatic admin configuration
โโโ tsconfig.json
โโโ package.json
โโโ vitest.config.ts # Test configuration
โโโ src/
โ โโโ config/
โ โ โโโ settings.json # i18n source of truth (runtime)
โ โ โโโ settings.ts # settings loader
โ โโโ config.ts # Site configuration
โ โโโ i18n/
โ โ โโโ config.ts # Locale resolver, path helpers
โ โ โโโ load.ts # Server-side dictionary loader (getDict)
โ โ โโโ runtime.ts # Client-side i18n runtime (lazy)
โ โ โโโ lookup.ts # Shared getNested/format helpers
โ โ โโโ i18n-client.ts # Client-side locale resolution
โ โ โโโ dictionaries/ # UI text translations
โ โ โโโ en.json
โ โ โโโ zh-CN.json
โ โ โโโ zh-TW.json
โ โ โโโ ja.json
โ โโโ layouts/
โ โ โโโ BaseLayout.astro # Base layout
โ โโโ components/
โ โ โโโ page-pages/ # Canonical page implementations
โ โ โ โโโ Index.astro
โ โ โ โโโ About.astro
โ โ โ โโโ Archive.astro
โ โ โ โโโ NotFound.astro
โ โ โ โโโ Post.astro
โ โ โ โโโ Tag.astro
โ โ โ โโโ Tags.astro
โ โ โโโ Header.astro
โ โ โโโ Footer.astro
โ โ โโโ PostCard.astro
โ โ โโโ Pagination.astro
โ โ โโโ TOC.astro
โ โ โโโ Search.astro
โ โ โโโ SettingsPanel.astro
โ โ โโโ LangSwitcher.astro
โ โ โโโ BackToTop.astro
โ โ โโโ SortToggle.astro
โ โ โโโ DiffViewer.astro
โ โโโ pages/ # Active Astro routes
โ โ โโโ index.astro # i18n redirect or flat index
โ โ โโโ en/, zh-CN/, zh-TW/, ja/ # Locale-prefixed routes (i18n=on)
โ โ โโโ _i18n_disabled/ # Inactive tree (managed by i18n-toggle.mjs)
โ โ โโโ flat/ # Flat thin wrappers (stashed when i18n=on)
โ โ โโโ root-index.astro
โ โโโ lib/
โ โ โโโ content.ts # Content collection helpers + reading time
โ โ โโโ utils.ts # formatDate, isExternalUrl, groupPostsByYear
โ โ โโโ search.ts # SearchEngine (lazy Fuse.js)
โ โ โโโ build-feed.ts # Shared RSS / search-index builders
โ โ โโโ translate.ts # Shared withFallback() helper
โ โ โโโ rehype-external-links.ts
โ โ โโโ types.ts
โ โ โโโ client/ # Client-side scripts (extracted from inline)
โ โ โโโ mobile-menu.ts
โ โ โโโ motion.ts
โ โ โโโ search.ts
โ โโโ styles/
โ โ โโโ global.css
โ โ โโโ interactivity.css
โ โโโ posts/ # Blog posts (locale/slug/index.mdoc)
โ โ โโโ en/
โ โ โโโ zh-CN/
โ โ โโโ zh-TW/
โ โ โโโ ja/
โ โโโ public/
โ โโโ theme-init.js # Inline theme initialization
โ โโโ favicon.svg
โ โโโ robots.txt
โโโ test/ # Vitest test suite
โ โโโ utils.test.ts
โ โโโ content.test.ts
โ โโโ i18n.test.ts
โโโ scripts/
โโโ i18n-toggle.mjs # i18n on/off toggle
โโโ new-post.mjs # Post scaffolding script
โโโ post-build-redirects.mjs
โโโ serve.mjs
Canonical page implementations live in src/components/page-pages/<Page>.astro and accept an optional lang prop. Both the active flat route and the i18n wrapper route are thin Astro files that render the canonical. getStaticPaths stays on the route (Astro requires it there).
- Go to Pages in Cloudflare Dashboard
- Create a project โ Connect to Git
- Configure build settings:
- Build command:
npm run build - Build output directory:
dist
- Build command:
- Save and Deploy
- Go to Settings โ Pages in your repo
- Select GitHub Actions as the Source
- Push to
mainโ GitHub Actions builds and deploys automatically
A pre-configured workflow is available at .github/workflows/deploy.yml.
If you created your repo from this template, you can pull in upstream updates at any time:
- Go to Actions โ Sync Upstream โ Run workflow
- A pull request will be created with the latest upstream changes
- Review conflicts (if any) and merge
| Technology | Purpose |
|---|---|
| Astro 5 | Static site generator |
| TypeScript | Type safety |
| KaTeX | LaTeX math rendering |
| Fuse.js | Fuzzy search (lazy-loaded) |
| rehype-katex | Markdown math |
| @astrojs/sitemap | Sitemap |
| @astrojs/rss | RSS feed |
| @astrojs/markdoc | Markdoc format support |
| Keystatic | Local admin UI |
| Vitest | Unit testing |
ๅบไบ Astro ๆๅปบ็็ฐไปฃๅ้ๆๅๅฎข๏ผๆฏๆ Markdown/MarkdocใLaTeX ๆฐๅญฆๅ ฌๅผใๅ จๆๆ็ดข๏ผๆๅ ่ฝฝ๏ผใๆ่ฒๆจกๅผใRSS ่ฎข้ ใๅค่ฏญ่จๅๆขใKeystatic ๅ ๅฎน็ฎก็๏ผๅ ็ฝฎ่ฎพ็ฝฎ้ขๆฟๅฏไธชๆงๅ่ฐๆดๅญไฝๅคงๅฐใๅ ๅฎนๅฎฝๅบฆ็ญใ
๐ ็คบไพ็ซ็น: blog.qwara.top
- ๐ Markdown / Markdoc โ Markdown ๆ Markdoc ็ผๅๆ็ซ ๏ผๆฏๆ Frontmatter
- ๐ LaTeX ๆฐๅญฆๅ ฌๅผ โ KaTeX ้ๆ๏ผ่กๅ ๅๅฑ็คบๅ ฌๅผ
- ๐ ๅค่ฏญ่จ โ ็้ขไธๆ็ซ i18n๏ผen / zh-CN / zh-TW / ja๏ผ๏ผๅฏ้ๆถๅผๅ ณ
- ๐ ๅ จๆๆ็ดข โ ๆๅปบๆถ็ๆ็ดขๅผ๏ผFuse.js ๅฎขๆท็ซฏๅณๆถๆ็ดข๏ผๆๅ ่ฝฝ๏ผ
- ๐ ๆ่ฒๆจกๅผ โ ไบฎ่ฒ/ๆ่ฒ/่ท้็ณป็ปไธ็งๆจกๅผ
- โ๏ธ ่ฎพ็ฝฎ้ขๆฟ โ ๅญไฝๅคงๅฐใๅ ๅฎนๅฎฝๅบฆใๅญไฝๅๆขใTOC ๆพ้ใ่ฏญ่จ
- ๐ ็ฎๅฝๅฏผ่ช โ ไพง่พน็ฎๅฝ๏ผๆปๅจ้ซไบฎ
- โฑ ้ ่ฏปๆถ้ฟ โ ไธญ่ฑๆๆททๅๅ ๅฎน่ชๅจไผฐ็ฎ
- ๐ท ๆ ็ญพ็ณป็ป โ ๆ็ซ ๆ ็ญพๅ็ฑปใๆ ็ญพไบใๅ้กตๅฏผ่ช
- ๐ก RSS ่ฎข้ โ ๆฏ็ง่ฏญ่จ็ฌ็ซ RSS Feed
- ๐ฑ ๅๅบๅผ่ฎพ่ฎก โ ้้ ๆก้ขๅ็งปๅจ็ซฏ
- ๐ ้กต้ข่ฟๆธกๅจ็ป โ ๅ ็ฝฎ View Transitions API
- ๐ Keystatic ็ฎก็ โ ๆฌๅฐๅ ๅฎน็ฎก็ๅๅฐ
- โ ๅๅ ๆต่ฏ โ Vitest ๆต่ฏๅฅไปถ
# ไธญๅฝๅคง้็จๆทๅฏๅ
้
็ฝฎ npm ้ๅ
npm config set registry https://registry.npmmirror.com
# 1. ๅ
้
git clone https://github.com/Qwara-chan/TerseBlog.git
cd TerseBlog
# 2. ๅฎ่ฃ
ไพ่ต
npm install
# 3. ๅฏๅจๅผๅๆๅกๅจ
npm run dev
# 4. ๆๅปบ
npm run build
# 5. ้ข่งๆๅปบ็ปๆ
npm run preview
# 6. ็ฑปๅๆฃๆฅ
npm run check
# 7. ่ฟ่กๆต่ฏ
npm test้่ฟ src/config/settings.json ๆงๅถ๏ผ
{
"i18nEnabled": true,
"defaultLocale": "zh-CN"
}npm run i18n # ๆฅ็ๅฝๅ็ถๆ
npm run i18n:on # ๅผๅฏๅค่ฏญ่จ
npm run i18n:off # ๅ
ณ้ญๅค่ฏญ่จ๏ผๅ่ฏญ่จๆจกๅผ๏ผๅผๅฏๅ 4 ็ง่ฏญ่จๅๅซ็จ URL ๅ็ผ่ฎฟ้ฎ๏ผ/en/ /zh-CN/ /zh-TW/ /ja/ใๆ น่ทฏๅพ / ่ชๅจ่ทณ่ฝฌๅฐ้ป่ฎค่ฏญ่จใ
ๆ็ซ ็ฟป่ฏๆพๅจ src/posts/<่ฏญ็ง>/<slug>/index.mdoc ็ฎๅฝไธใ็ผบ่ฏๆถ่ชๅจๅ้ๅฐ้ป่ฎค่ฏญ็ง๏ผzh-CN๏ผใ
้กน็ฎๅ ็ฝฎ Keystatic ็จไบๆฌๅฐๅ ๅฎน็ฎก็๏ผ
npm run build # SKIP_KEYSTATIC=true๏ผCI ้ป่ฎคๆ้ค๏ผ
SKIP_KEYSTATIC=false npm run build # ๅฏ็จ Admin UI้
็ฝฎไฝไบ keystatic.config.tsใ
่ฏฆ่งไธๆน Deployment ็ซ ่ใ
ๅฆๆ็จๆญคๆจกๆฟๅๅปบไบไปๅบ๏ผไฝ ๅฏไปฅ้ๆถๆๅไธๆธธๆดๆฐ๏ผ
- ่ฟๅ ฅ Actions โ Sync Upstream โ Run workflow
- ไผ่ชๅจๅๅปบไธไธชๅ ๅซไธๆธธๆๆฐๅๆด็ Pull Request
- ๅฎกๆฅๅนถๅค็ๅๅๅนถๅณๅฏ
| ๆๆฏ | ็จ้ |
|---|---|
| Astro 5 | ้ๆ็ซ็น็ๆๅจ |
| TypeScript | ็ฑปๅๅฎๅ จ |
| KaTeX | LaTeX ๆฐๅญฆๅ ฌๅผๆธฒๆ |
| Fuse.js | ๆจก็ณๆ็ดข๏ผๆๅ ่ฝฝ๏ผ |
| @astrojs/sitemap | ็ซ็นๅฐๅพ |
| @astrojs/rss | RSS ่ฎข้ |
| @astrojs/markdoc | Markdoc ๆ ผๅผๆฏๆ |
| Keystatic | ๆฌๅฐ็ฎก็ๅๅฐ |
| Vitest | ๅๅ ๆต่ฏ |