lyrstar.com

用 Astro 重做个人站:博客与工具页共存的一种结构

2 分钟
目录

把个人站点从「只有文章」扩展成「文章 + 工具」之后,最先要回答的问题是:这两类页面的技术诉求并不一致。

文章页追求的是纯静态、首屏快、SEO 友好,最好连 JavaScript 都不加载;工具页却天然需要交互——输入、计算、复制、下载。如果为了工具页给整站套上一个前端框架,文章页就要白白背上运行时开销。

为什么是 Astro

Astro 的孤岛架构(Islands)刚好把这件事拆开了:

  • 页面默认编译成零 JavaScript 的静态 HTML
  • 只有显式标记为交互的组件才会加载对应脚本
  • Markdown/MDX 内容集合是一等公民,还带 schema 校验

于是文章页是真正的静态页,工具页也只加载它自己那一小段逻辑。两者互不干扰。

内容集合带 schema 校验

文章的 frontmatter 很容易写错:日期格式不统一、标签忘了写成数组、字段名打错。Astro 的内容集合可以直接用 Zod 定义结构:

const blog = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

写错字段构建阶段就会报错,而不是等页面渲染出一个 Invalid Date。z.coerce.date() 让 frontmatter 里直接写 2026-10-15 就能用,不必套引号再手动解析。

工具页不引入框架

十来个小工具,每个的交互都不复杂:读输入、算结果、写输出。这种规模用原生 DOM API 就够了,组件框架带来的收益抵不过它的体积。

共用的部分抽成一个模块——复制到剪贴板、下载文件、错误提示、输入防抖——各工具按需引入:

import { bindCopyButtons, debounce, setError } from '../../lib/ui';

Astro 会把 <script> 里的 import 一起打包并做 tree-shaking,所以只用到两个函数的页面不会把整个模块拖进来。

一个清单驱动整个工具区

工具的元信息(名称、描述、分类、搜索关键词)集中放在一份清单里,总览页、分类分组、即时搜索全部从它生成。新增工具时只需要写页面本体,再往清单里加一条记录,导航和搜索会自动带上它——不必再去改三四个地方。

还值得做的

  • 工具页加键盘快捷键(比如 Ctrl + Enter 执行)
  • 常用工具的输入内容做本地草稿保存
  • 文章加全文搜索

这套结构的好处是,这些都可以后面单独加,不需要动已有的部分。