用 Astro 搭建多主题博客:架构设计与实现
为什么选择 Astro
从 Hexo 迁移到 Astro,主要看中几点:
- 内容驱动:天然支持 Markdown,不需要额外配置
- 静态生成:构建时生成所有页面,访问速度快
- Islands 架构:按需加载 JS,不用不加载
项目结构
src/├── components/ # 组件├── content/blog/ # Markdown 文章├── layouts/ # 布局模板├── pages/ # 页面路由├── styles/ # 样式(按组件拆分)├── utils/ # 工具函数├── types.ts # 类型定义└── content.config.ts # 内容 schema多主题系统的实现
设计思路
主题切换的核心是 CSS 变量。每个主题定义一套颜色变量,切换时只需改变 data-theme 属性。
[data-theme="sakura"] { --bg: #fef8fa; --accent: #d06090; /* ... */}
[data-theme="starry"] { --bg: #1a1a2e; --accent: #7b8cde; /* ... */}配置化管理
主题配置抽离到 src/utils/themes.ts:
export const themes = [ { id: 'sakura', name: '樱花' }, { id: 'starry', name: '星空' },];
export const defaultTheme = 'sakura';Header 组件从配置读取主题列表,以后加新主题只需改配置和 CSS,不用动组件。
持久化选择
用户选择的主题保存到 localStorage,下次访问自动恢复:
// 切换时保存localStorage.setItem('theme', theme);
// 页面加载时恢复(内联在 head 中,避免闪烁)const theme = localStorage.getItem('theme') || 'sakura';document.documentElement.setAttribute('data-theme', theme);主题自己负责
每个主题定义 --theme-label 变量,用于菜单中显示自己的颜色:
[data-theme="sakura"] { --theme-label: #d06090;}这样不用在组件中硬编码颜色,新增主题时不需要改其他地方。
标签筛选
生成静态路径
Astro 的 getStaticPaths 可以动态生成页面。标签页根据所有文章的标签自动生成:
export async function getStaticPaths() { const posts = await getCollection('blog'); const allTags = posts.flatMap((p) => p.data.tags); const uniqueTags = [...new Set(allTags)];
return uniqueTags.map((tag) => ({ params: { tag }, props: { tag }, }));}筛选文章
在页面中筛选包含当前标签的文章:
const posts = (await getCollection('blog')) .filter((p) => !p.data.draft && p.data.tags.includes(tag)) .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());CSS 按组件拆分
800+ 行的 CSS 拆分成 9 个文件:
base.css- 主题变量、基础样式header.css- 头部导航theme-switcher.css- 主题切换post-card.css- 文章卡片prose.css- 文章内容排版sidebar.css- 侧边栏search.css- 搜索footer.css- 底部pages.css- 关于、404 页
global.css 只负责导入:
@import './base.css';@import './header.css';@import './theme-switcher.css';/* ... */类型统一
Astro 的内容 schema 和组件 Props 有重复定义,用 z.infer 从 schema 推导类型:
export const blogSchema = z.object({ title: z.string(), description: z.string(), pubDate: z.coerce.date(), tags: z.array(z.string()).default([]), draft: z.boolean().default(false),});
// types.tsimport type { blogSchema } from './content.config';export type BlogPost = z.infer<typeof blogSchema>;改字段只需改 content.config.ts,类型自动同步。
搜索功能
使用 Pagefind,构建时自动生成搜索索引,零运行时依赖:
astro build && pagefind --site dist搜索弹窗用原生 JS 实现,支持 Ctrl+K 快捷键和 Esc 关闭。
总结
这个博客的架构原则:
- 配置化:主题、标签等可变部分抽离配置
- 组件化:样式按组件拆分,找起来快
- 类型安全:从 schema 推导类型,改一处生效
- 渐进增强:没有 JS 也能正常浏览,JS 只增强体验
用 Astro 搭建多主题博客:架构设计与实现
https://kurikana.cn/blog/astro-multi-theme-blog/