Personal website and blog built with Astro, Tailwind CSS v4, and deployed on Cloudflare Pages.
- Static site generation (zero client-side JS framework)
- Internationalization (English & Traditional Chinese) with hreflang + localized sitemap
- Dark mode with system preference detection
- Blog from Markdown files with syntax highlighting, table of contents, and prev/next navigation
- Optimized responsive images via
astro:assets(WebP, lazy loading) - RSS feeds per locale (
/en/rss.xml,/zh/rss.xml) - Tag archive pages (
/en/tags/...,/zh/tags/...) - SEO: canonical URLs, Open Graph, JSON-LD structured data, sitemap
- Self-hosted Inter font + system CJK font stack
- Responsive design with shadcn/ui aesthetic
npm install
npm run dev # Start dev server at http://localhost:4321
npm run build # Build static site to dist/
npm run preview # Preview the build locally- Push this repo to GitHub
- In Cloudflare Pages, create a new project and connect the repo
- Configure:
- Build command:
npm run build - Output directory:
dist - Node.js version:
20(set via environment variableNODE_VERSION=20)
- Build command:
Redirects (including the / → /en root redirect and legacy URL redirects) live in public/_redirects.
Create a .md file in the /blog/ directory with this frontmatter:
---
title: "Your Post Title"
description: "A brief description (used for SEO and RSS)"
date: 2025-03-01
locale: "en" # "en" or "zh"
tags: ["tag1", "tag2"]
image: "./images/your-post/cover.jpg" # optional, relative to /blog/
imageAlt: "What the cover shows" # optional
draft: false # optional, defaults to false
---
Your markdown content here...Posts are automatically discovered — no manual route registration needed.
Bilingual pairing: name the files my-post.md (en) and my-post-zh.md (zh). Both share the URL slug my-post; the language switcher links between them automatically.
Put post images in blog/images/<post-name>/ and reference them relatively so Astro optimizes them (resizing, WebP, lazy loading):
Compress large photos first (resizes to 1600px, ~80% quality):
node scripts/optimize-images.mjs # edit MAPPINGS inside for new filesUse ASCII file names (no spaces or CJK characters).
- All routes are prefixed:
/en/*and/zh/* - The root
/redirects to/en(server-side via_redirects) - Set
locale: "en"orlocale: "zh"in blog post frontmatter - The language switcher preserves the current path and falls back to the blog index when a translation doesn't exist
- UI strings live in
src/i18n/ui.ts
IceHome/
├── blog/ # Markdown blog posts
│ └── images/ # Post images (optimized at build time)
├── public/ # Static assets served as-is (_redirects, robots.txt, og-default.png)
├── scripts/ # optimize-images.mjs, generate-og-default.mjs
├── src/
│ ├── components/ # Astro components
│ ├── i18n/ # Translations and i18n utilities
│ ├── layouts/ # Page layouts
│ ├── lib/ # posts.ts (translation pairing), utils.ts
│ ├── pages/ # File-based routes (en/, zh/, rss.xml per locale)
│ ├── plugins/ # rehype-figure (caption rendering)
│ └── styles/ # Global CSS (design tokens, CJK typography)
├── astro.config.mjs
└── package.json