用 Astro 搭建一个静态博客

从零开始,用 Astro 的内容集合(Content Collections)组织 Markdown,构建期生成静态页面,顺便聊聊为什么静态站点正在回归。

2 分钟阅读 605 字 zero
目录(5 节)

静态站点生成器(SSG)这几年悄悄回到了舞台中央。原因不复杂:内容为主的网站根本不需要服务器渲染每一篇文章。

为什么选 Astro

比较过 Hexo、Hugo、Eleventy,最后选了 Astro。理由有三条:

  1. 默认零 JavaScript。页面只是 HTML 和 CSS,除非你显式写了 <script>
  2. 内容集合类型安全。Markdown 的 frontmatter 用 Zod 校验,写错字段会在构建时直接报错
  3. 可以局部上交互。搜索框这种需要脚本的地方,用普通的 <script> 就行,不会被强行塞进框架运行时

内容集合怎么用

Astro 5 之后引入了 Content Layer API,配置文件从 src/content/config.ts 挪到了 src/content.config.ts

import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';

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

export const collections = { blog };

注意 z 要从 astro/zod 导入。从 astro:content 导入虽然在部分版本还能用,但已经是过时写法了。

路由与静态路径

文章页用 rest 参数匹配,这样将来想把文章放在子目录里也不用改路由:

---
import { getCollection, render } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { id: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content, headings } = await render(post);
---

<article>
  <h1>{post.data.title}</h1>
  <Content />
</article>

有个容易踩的坑:getStaticPaths 返回的 params必须是字符串。数字会被静默忽略,然后你会在构建产物里找不到那个页面。

阅读时长

我没有引入 remark-reading-time 这类插件。Astro 7 默认用 Sätteri 处理 Markdown,要挂 remark 插件得额外装 @astrojs/markdown-remark 并改配置。既然只是数一下字数,不如自己算:

function countWords(text: string): number {
  const cjk = text.match(/[\u4e00-\u9fff]/g)?.length ?? 0;
  const latin = text.replace(/[\u4e00-\u9fff]/g, ' ');
  const words = latin.match(/[A-Za-z0-9'-]+/g)?.length ?? 0;
  return cjk + words;
}

中文按字算,英文按词算,最后套一个「中文 400 字/分钟、英文 220 词/分钟」的经验值。不精确,但够用了。

部署

npm run build 之后,dist/ 就是一个完整的网站,扔到任何静态托管上都能跑。Cloudflare Pages、Vercel、Netlify、GitHub Pages 都行,不需要 Node 运行时。

静态的代价是构建时间随文章数量线性增长。不过对于个人博客,几千篇文章也才几秒钟,完全不是问题。

  • Vibe Coding 全流程提示词规范

    以 AI 为核心驱动的五阶段提示词工作流:从需求方案到代码实现、测试、调试与提交,附可直接复用的 Prompt 模板。

    3 分钟阅读 1265 字
  • 深色模式的三个细节

    主题切换看起来很简单,但要做得不闪、不刺眼、不违和,有几个容易忽略的地方。

    2 分钟阅读 687 字