Skip to content
Public template

About

A minimalist black & white Astro blog

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

ย 

History

73 Commits

Folders and files

Repository files navigation

TerseBlog

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


ไธญๆ–‡็‰ˆๆœฌ

Features

  • ๐Ÿ“ 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

Quick Start

# 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 test

Multi-language (i18n)

i18n 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.

Keystatic Admin

The project includes Keystatic for local content management. Build without it in CI:

npm run build          # SKIP_KEYSTATIC=true by default

To enable the admin UI, build without the skip flag:

SKIP_KEYSTATIC=false npm run build

Admin config lives in keystatic.config.ts โ€” defines per-locale post collections and site settings singleton.

Project Structure

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

Page architecture

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).

Deployment

Cloudflare Pages

  1. Go to Pages in Cloudflare Dashboard
  2. Create a project โ†’ Connect to Git
  3. Configure build settings:
    • Build command: npm run build
    • Build output directory: dist
  4. Save and Deploy

GitHub Pages

  1. Go to Settings โ†’ Pages in your repo
  2. Select GitHub Actions as the Source
  3. Push to main โ€” GitHub Actions builds and deploys automatically

A pre-configured workflow is available at .github/workflows/deploy.yml.

Keeping in Sync

If you created your repo from this template, you can pull in upstream updates at any time:

  1. Go to Actions โ†’ Sync Upstream โ†’ Run workflow
  2. A pull request will be created with the latest upstream changes
  3. Review conflicts (if any) and merge

Tech Stack

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

License

CC BY-NC-SA 4.0


ไธญๆ–‡

TerseBlog

ๅŸบไบŽ 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 ๅ†…ๅฎน็ฎก็†

้กน็›ฎๅ†…็ฝฎ Keystatic ็”จไบŽๆœฌๅœฐๅ†…ๅฎน็ฎก็†๏ผš

npm run build                  # SKIP_KEYSTATIC=true๏ผˆCI ้ป˜่ฎคๆŽ’้™ค๏ผ‰
SKIP_KEYSTATIC=false npm run build   # ๅฏ็”จ Admin UI

้…็ฝฎไฝไบŽ keystatic.config.tsใ€‚

้ƒจ็ฝฒ

่ฏฆ่งไธŠๆ–น Deployment ็ซ ่Š‚ใ€‚

ๅŒๆญฅไธŠๆธธๆ›ดๆ–ฐ

ๅฆ‚ๆžœ็”จๆญคๆจกๆฟๅˆ›ๅปบไบ†ไป“ๅบ“๏ผŒไฝ ๅฏไปฅ้šๆ—ถๆ‹‰ๅ–ไธŠๆธธๆ›ดๆ–ฐ๏ผš

  1. ่ฟ›ๅ…ฅ Actions โ†’ Sync Upstream โ†’ Run workflow
  2. ไผš่‡ชๅŠจๅˆ›ๅปบไธ€ไธชๅŒ…ๅซไธŠๆธธๆœ€ๆ–ฐๅ˜ๆ›ด็š„ Pull Request
  3. ๅฎกๆŸฅๅนถๅค„็†ๅŽๅˆๅนถๅณๅฏ

ๆŠ€ๆœฏๆ ˆ

ๆŠ€ๆœฏ ็”จ้€”
Astro 5 ้™ๆ€็ซ™็‚น็”Ÿๆˆๅ™จ
TypeScript ็ฑปๅž‹ๅฎ‰ๅ…จ
KaTeX LaTeX ๆ•ฐๅญฆๅ…ฌๅผๆธฒๆŸ“
Fuse.js ๆจก็ณŠๆœ็ดข๏ผˆๆ‡’ๅŠ ่ฝฝ๏ผ‰
@astrojs/sitemap ็ซ™็‚นๅœฐๅ›พ
@astrojs/rss RSS ่ฎข้˜…
@astrojs/markdoc Markdoc ๆ ผๅผๆ”ฏๆŒ
Keystatic ๆœฌๅœฐ็ฎก็†ๅŽๅฐ
Vitest ๅ•ๅ…ƒๆต‹่ฏ•

่ฎธๅฏ่ฏ

CC BY-NC-SA 4.0

About

A minimalist black & white Astro blog

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages