An Astro theme that believes text itself is beautiful. Typography drives the design. Words speak for themselves. ✍️
Good writing deserves good typography. Most themes bury your words under hero images, animations, and UI chrome. astro-theme-note takes the opposite approach -- it lets text be the design.
Elegant serif headings, a clean reading rhythm, and a layout that stays out of your way. Everything serves one goal: making your writing look and feel beautiful.
| astro-theme-note | Typical Astro Theme | Hugo Theme | Jekyll Theme | |
|---|---|---|---|---|
| JavaScript output | ~0.5 KB (inline) | 50-200 KB | 0 KB | 0-50 KB |
| CSS | Tailwind CSS v4, @theme design tokens |
Tailwind / framework | Varies | Varies |
| Typography | Cross-platform (ui-serif, Georgia, system-ui) |
Theme-dependent | Theme-dependent | Theme-dependent |
| Dark mode | Light / Dark / System toggle | JS toggle | Varies | Varies |
| i18n | Built-in Astro i18n | Plugin / manual | Built-in | Plugin |
| AI-agent ready | llms.txt | No | No | No |
| Build tool | Astro (npm) | Astro (npm) | Hugo (Go binary) | Jekyll (Ruby) |
| Content format | Markdown | Markdown / MDX | Markdown | Markdown |
| Setup complexity | npm install → write |
Install → configure → write | Install Go → configure → write | Install Ruby → configure → write |
| Deploy | Cloudflare Pages (built-in) | Varies | Varies | GitHub Pages |
- Cross-platform typography -- Serif headings (
ui-serif/ Georgia) paired with clean body text (system-ui+ CJK fallbacks), beautiful and consistent across macOS, Windows, Linux, and Android - Dark mode -- Light / Dark / System toggle with localStorage persistence, carefully tuned for reading comfort in both modes
- Near-zero JavaScript -- Only ~0.5 KB inline script for theme toggle, no bundled JS
- Tailwind CSS v4 -- Utility-first styling with
@themedesign tokens, easy to customize - Astro Content Collections -- Type-safe Markdown posts with frontmatter validation
- RSS feed -- Built-in
/rss.xmlvia@astrojs/rss - Sitemap -- Auto-generated via
@astrojs/sitemap - SEO -- Open Graph meta tags, canonical URLs, per-post descriptions
- Responsive -- Mobile-friendly layout that preserves reading rhythm across screen sizes
- Draft support -- Set
draft: truein frontmatter to hide posts from listing and feed - Cloudflare Pages ready -- Deploy workflow included with preview URLs on PRs, push to
mainand it's live - i18n -- Built-in Astro i18n routing with language switcher (English, 简体中文, 繁體中文)
- llms.txt -- AI-agent-friendly
/llms.txtendpoint for LLM discoverability - Google Analytics -- Optional, zero-config via
PUBLIC_GA_IDenv var, runs in a Partytown Web Worker - Tested -- Unit tests (Vitest) + E2E tests (Playwright), integrated into CI
- Click "Use this template" → "Create a new repository"
- Clone your new repo:
git clone https://github.com/YOUR_USERNAME/YOUR_REPO.git
cd YOUR_REPO- Install dependencies:
npm install- Configure your site:
# astro.config.mjs -- set your site URL
site: 'https://YOUR_USERNAME.github.io'
# src/config.ts -- set title and nav links- Start writing:
npm run dev- Deploy: push to
main, GitHub Actions builds and deploys automatically.
git clone https://github.com/justinhuangcode/astro-theme-note.git my-blog
cd my-blog
rm -rf .git && git init
npm install
npm run devCreate Markdown files in src/content/posts/:
---
title: Your Post Title
date: 2026-01-01
description: Optional description for SEO
tags: [optional, tags]
draft: false
---
Your content here.| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Post title |
date |
date | Yes | Publication date (YYYY-MM-DD) |
description |
string | No | Used in RSS feed and meta tags |
tags |
string[] | No | Post tags |
draft |
boolean | No | true hides from listing and feed (default: false) |
| Command | Description |
|---|---|
npm run dev |
Start local dev server |
npm run build |
Build static site to dist/ |
npm run preview |
Preview production build locally |
npm test |
Run unit and component tests (Vitest) |
npm run test:e2e |
Run E2E tests (Playwright) |
export const SITE = {
title: 'Note',
description: 'A minimal blog',
};
export const ANALYTICS = {
googleId: import.meta.env.PUBLIC_GA_ID || '',
};
export const NAV_LINKS = [
{ labelKey: 'nav.blog' as const, href: '/' },
{ labelKey: 'nav.about' as const, href: '/about' },
];import tailwindcss from '@tailwindcss/vite';
import partytown from '@astrojs/partytown';
export default defineConfig({
site: 'https://your-domain.pages.dev', // Required for RSS and sitemap
vite: {
plugins: [tailwindcss()],
},
integrations: [sitemap(), partytown()],
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh-hans', 'zh-hant'],
routing: { prefixDefaultLocale: false },
},
});src/
├── config.ts # Site title, description, nav links
├── content.config.ts # Content Collections schema
├── i18n/
│ ├── ui.ts # Translation strings & language definitions
│ └── utils.ts # i18n helpers (getLangFromUrl, useTranslations, etc.)
├── layouts/
│ └── Layout.astro # Global layout (head, nav, theme & language switcher)
├── pages/
│ ├── index.astro # Home (English, default locale)
│ ├── about.astro # About page
│ ├── 404.astro # Not found page
│ ├── rss.xml.ts # RSS feed (English)
│ ├── llms.txt.ts # AI-agent-friendly llms.txt (all languages)
│ ├── posts/
│ │ └── [id].astro # Post detail (English)
│ ├── zh-hans/ # Simplified Chinese pages
│ │ ├── index.astro
│ │ ├── about.astro
│ │ ├── rss.xml.ts
│ │ └── posts/[id].astro
│ └── zh-hant/ # Traditional Chinese pages
│ ├── index.astro
│ ├── about.astro
│ ├── rss.xml.ts
│ └── posts/[id].astro
├── content/
│ └── posts/
│ ├── *.md # English posts (default)
│ ├── zh-hans/*.md # Simplified Chinese posts
│ └── zh-hant/*.md # Traditional Chinese posts
└── styles/
└── global.css # Tailwind CSS v4 @theme tokens + base styles
public/
└── favicon.svg # Site favicon
tests/
├── unit/ # Unit tests (Vitest)
├── component/ # Component tests (Vitest)
└── e2e/ # E2E tests (Playwright)
.github/
└── workflows/
└── ci.yml # CI tests + Cloudflare Pages deployment
The included workflow (.github/workflows/ci.yml) deploys to Cloudflare Pages automatically:
- Push to
main→ production deployment (your-project.pages.dev) - Pull request → preview deployment with unique URL, posted as a PR comment
Setup:
- Create a Cloudflare Pages project (name:
astro-theme-note) - Add repository secrets:
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID - Push to
main-- the site deploys automatically
Analytics is disabled by default. To enable it, add a repository variable (not secret) in your GitHub repo:
- Go to Settings > Secrets and variables > Actions > Variables
- Click New repository variable
- Name:
PUBLIC_GA_ID, Value: your Measurement ID (e.g.G-XXXXXXXXXX)
The gtag script runs inside a Partytown Web Worker, so it won't block your page rendering.
For local development, you can pass the variable directly:
PUBLIC_GA_ID=G-XXXXXXXXXX npm run devOr hardcode it in src/config.ts:
export const ANALYTICS = {
googleId: 'G-XXXXXXXXXX',
};Since the output is static HTML in dist/, you can deploy anywhere:
npm run build
# Upload dist/ to Netlify, Vercel, GitHub Pages, or any static host- Typography is the design -- Serif headings in
ui-serif/ Georgia, clean body text insystem-ui, and a carefully tuned reading rhythm. The typeface is the visual identity. - Text is beautiful -- No hero images, no gradients, no animations. Well-set text on a quiet page is the most elegant interface.
- Works everywhere -- Cross-platform font stacks with CJK-aware fallbacks (PingFang SC, Microsoft YaHei). No web fonts to load, no FOIT, no layout shift. Beautiful on every OS.
- Crafted, not complex -- Tailwind CSS v4
@themedesign tokens make it easy to customize without learning a framework. Two files to edit:config.tsandastro.config.mjs.
Inspired by yinwang.org.
Contributions are welcome. Please open an issue first to discuss what you'd like to change.