RETURN_TO_INDEX

RESEARCH_ENTRY // 前端

用 Astro 构建低 JavaScript 内容站

利用内容集合与静态路由,为技术博客建立类型安全的内容工作流。

内容优先的技术选择

博客的大部分页面在发布后不会频繁变化。Astro 默认输出静态 HTML,只在搜索、主题切换等必要区域加载脚本,非常适合这类内容型网站。

选择框架时我关注的不是“能不能做出页面”,而是内容增长后的维护成本:文章元数据能否被校验,路由能否自动生成,RSS 与站点地图是否容易接入,以及页面是否在没有 JavaScript 时仍然可读。

Astro 的组件在构建阶段执行,默认不会把组件运行时发送到浏览器。对博客来说,这意味着导航、文章列表、标签页和正文可以直接输出 HTML;只有真正需要状态的搜索框、主题切换等功能才加载少量脚本。

内容集合

内容集合通过 Schema 约束文章元数据。在编写 Markdown 时,就能提前发现日期格式、标签类型或必填字段的问题。下面是本站使用的简化版本:

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

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

export const collections = { blog };

这样做的价值不只是类型提示。构建失败会直接指出哪篇文章缺少标题、日期写错或标签不是数组,问题不会等到部署后才暴露。

从内容生成路由

列表页先读取集合、过滤草稿并按日期排序;详情页通过 getStaticPaths 为每篇文章生成路径。分类、标签、归档和 RSS 都应该复用同一份内容数据,而不是分别维护数组。

const posts = (await getCollection('blog', ({ data }) => !data.draft))
  .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

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

路由来自文件名,标题则来自 frontmatter。文件名一旦发布就尽量不要修改;如果必须调整,应配置重定向,避免外部链接和搜索引擎索引失效。

保持页面轻量

每个交互组件都应该回答一个问题:它是否真的需要在浏览器中运行?能用 HTML 和 CSS 完成的部分,不必交给客户端框架。

本站采用的策略是:正文、分页和导航完全静态;主题切换用一小段原生脚本;全文搜索在构建结束后由 Pagefind 扫描生成索引。这样搜索能力不会要求站点运行数据库或服务器函数。

{
  "scripts": {
    "build": "astro build && pagefind --site dist"
  }
}

如果使用 React/Vue/Svelte 组件,Astro 也不会默认水合。只有加上 client:loadclient:idleclient:visible 等指令,组件代码才会进入浏览器。应选择最晚且满足体验的加载时机。

内容站不能漏掉的基础设施

一套可持续的写作流程

我的发布流程是:新建 Markdown、填写 frontmatter、本地预览、运行构建、检查死链与移动端布局,最后提交。日期与标签由 Schema 校验,构建产物则验证所有静态路由和搜索索引。

内容站的性能通常不是靠复杂优化得到的,而是从架构上避免不必要的客户端运行时。让 HTML 承担内容,让少量 JavaScript 只承担交互,长期维护会轻松很多。