Table of Contents
This post records the static-site architecture on Astro 5 and subsequent engineering changes.
Stack
| Layer | Choice |
|---|---|
| Framework | Astro 5 (output: 'static') |
| Interactivity | React (search, selected client scripts) |
| Styles | Global CSS + Tailwind 4 |
| Math | remark-math + rehype-katex |
| Search | Pagefind (indexes dist/ after build) |
| Deploy | GitHub Actions → rsync → Nginx static root |
Build command: pnpm assets:sync && astro build && pagefind --site dist.
Content model
Collections are defined in src/content.config.ts:
| Collection | Path | Role |
|---|---|---|
blog | src/content/blog/{zh-Hans,en}/ | Site log |
physics | src/content/physics/{zh-Hans,en}/ | Physics articles |
programming | src/content/programming/{zh-Hans,en}/ | Programming articles |
teaching | src/content/teaching/{zh-Hans,en}/ | Teaching articles |
pages | src/content/{about,index}.*.mdx | About page and homepage SEO metadata |
Article collections share frontmatter: title, description, pubDate, updatedDate, tags, categories, permaLink, pinned, draft, comments, heroImage, author (comments defaults to on; only false disables comments for that post).
The pages collection does not require pubDate. about.*.mdx renders full MDX bodies; index.*.mdx exposes only title and description to routes (see homepage split below).
Published entries pass through getPublishedCollection(), which excludes draft: true.
Bilingual routing
Locales are physically separated: distinct content folders and URL prefixes, not in-page locale toggles on shared content.
| Locale | URL prefix | Content folder suffix |
|---|---|---|
| Simplified Chinese | /zh-Hans/ | zh-Hans/ |
| English | /en/ | en/ |
astro.config.mjs: prefixDefaultLocale: true, redirectToDefaultLocale: false.
Root / (src/pages/index.astro):
- Emits Chinese homepage static HTML at build time (for crawlers).
LocaleRedirect.astrosends browsers to/zh-Hans/or/en/vialocalStorage.locale-preferenceornavigator.languages.- With JavaScript disabled, the root Chinese HTML remains.
Article URLs are built in src/utils/post-taxonomy.ts as /{locale}/{collection}/{slug}/. slug prefers permaLink, otherwise derived from the entry id.
Each collection exposes /categories/[category] and /tags/[tag] archives. Legacy paths such as /blog/categories/... remain as redirect stubs.
Page layout
Homepage
| File / component | Role |
|---|---|
src/pages/{zh-Hans,en}/index.astro | Loads metadata from the matching index.*.mdx in pages |
src/components/HomePageLayout.astro | <head> SEO, SeoJsonLd, page shell |
src/components/HomeLanding.astro | Hero, five section cards, featured posts, photography strip |
src/content/index.*.mdx | title and description only (body not rendered) |
Article detail
src/layouts/BlogPost.astro: hero image, dates, language switch, MDX body; Waline integrated but site-wide off by default (waline-config.ts → enabled: false). Hero resolution: src/utils/hero-image.ts.
Article index
src/components/PostIndexPage.astro: pinned dual-column header (pinned: true or latest post), two-column list, category/tag filter links.
About
src/pages/{locale}/about.astro renders the full about.*.mdx body.
Photography
Photography is not a content collection. Data comes from src/assets/photos/ and config.json, assembled in src/utils/photography.ts.
| Route | Component |
|---|---|
/{locale}/photography/ | PhotographyIndexPage.astro |
/{locale}/photography/[category]/ | PhotographyCategoryPage.astro |
| Homepage strip | PhotographyStrip.astro |
Article system configuration
Per-collection behavior lives in src/utils/post-collection-config.ts:
enableCategories: whether category archive pages are generated.categoryArticleNavigation: whether previous/next inside a category context stays within that category (all four article collections usecategorytoday).- Localized index titles and filter labels.
List ordering (orderPostsForIndex): pinned posts first, then by descending pubDate. Multiple pinned: true entries: the latest by date occupies the header slot.
Previous/next logic is in src/utils/post-routing.ts and supports category/tag filter context.
Comments (Waline):
- Site-wide toggle:
src/utils/waline-config.ts→enabled(defaultfalse) - Per-post: when site-wide is on, omit
commentsor settrueto show the trigger;comments: falsedisables that post - Implementation:
WalineComments.astro+discussion-lazy.ts/discussion-mount.ts(click-to-load), below the article body
Image pipeline
Two-tier storage:
| Kind | Originals (not in Git) | Display (in Git) |
|---|---|---|
| General | src/assets/images-originals/ | src/assets/images/*.avif + config.json |
| Photography | src/assets/photos-originals/<category>/ | src/assets/photos/ + config.json, featured.json, _category.json |
Sync entry: script/prepare-photos.mjs (pnpm assets:sync). Display assets are avif, max edge ≤ 2048px; the display tree mirrors the originals tree.
Behavior flags (src/consts.ts → SITE_IMAGE_SETTINGS):
enableAstroImageOptimizationenableOriginalImageOnZoom(whether zoom requests server/files/images-originals/etc.)
Site-wide zoom uses the inline lightbox in Header.astro (site-lightbox).
RSS and search
RSS (src/pages/{locale}/rss.xml.js):
- Aggregates published posts from
blog,physics, andprogramming(notteaching). - Chinese feed uses
SITE_DESCRIPTION; English feed usesSITE_DESCRIPTION_EN(src/consts.ts).
Search:
pagefind --site distruns at the end of build.- UI: native search modal in
Header.astro, plusSearchResults.jsxandSearchResultList.jsx; usepnpm build && pnpm previewto verify Pagefind (disabled inpnpm dev).
Deployment
git push → GitHub Actions builds dist/ → rsync to the server static root. Rsync excludes server-side /files/images-originals/ and /files/photos-originals/.
Revision log
2026-05-04 (initial)
- Five content collections and
post-taxonomyURL generation. - Parallel zh/en routes,
HomeLandinghomepage, Pagefind search, RSS over three collections. - Photography module and
prepare-photos.mjssync pipeline.
2026-06-20
- Homepage SEO split: removed unused body from
index.*.mdx; UI remains inHomeLanding.astro. - Site descriptions: expanded
SITE_DESCRIPTION, addedSITE_DESCRIPTION_ENfor the English RSS feed. - Dead code removal: deleted unreferenced
Aside.astro,MediumZoom.jsx, and themedium-zoomdependency; removed sidebar CSS leftovers; renamed lightbox loader class tosite-lightbox__loader-ring. - Category metadata:
IBDP.mdcategories changed from “IB Physics” toIBDP; aligned some programming en/zh category strings. - Dev docs: rewrote
.github/copilot-instructions.mdfor the current multi-collection layout.
2026-06-25
- Subfolder categories:
physics,programming,teaching, andbloguse folder-based routes and sub-index pages; optionalindex.mdintro copy at collection or subfolder root;pubDate/descriptionoptional on index posts. - Navigation and search: desktop category dropdowns, mobile horizontal subcategory chips and menu dividers; search rebuilt with native modal shell +
SearchResults.jsxportal; preview close button and result scrolling fixed. - Content migration: articles reorganized into subfolders; legacy physics URLs kept via
astro.config.mjsredirects.
2026-06-23
- Waline comments: integrated
@waline/clientwith self-hosted servercomment.physchen.com; click-to-load; site-wide default off (waline-config.ts→enabled: false). - Favicon: full icon set in
public/with references inBaseHead.astro. - Docs: README and copilot instructions updated with comment, favicon, and enablement notes.
Planned architecture work
The items below target site structure and UX only, not editorial plans for any section.
- Homepage and section entrances: adjust layout hierarchy and block structure on top of
HomeLanding. - Article system: archive and list density; discovery paths via categories, tags, and search.
- Interaction and responsive behavior: mobile nav, lightbox, and previous/next consistency.
- Photography module: category pages, single-photo view, and homepage featured strip.
- Engineering and metadata: routing symmetry, content schema, SEO frontmatter split, build/deploy pipeline, dependency maintenance.