从 Typecho 迁移到 Astro:我的静态博客重构记
记录把博客从 PHP 的 Typecho 迁移到 Astro 静态框架的全过程,包括内容导出、数据映射、主题重建和踩过的坑。
用了好几年的 Typecho 博客,前阵子终于下定决心迁移到 Astro。整个过程不算轻松,但回头看是值得的。这篇文章记录一下迁移的思路、过程和踩过的坑,给有同样想法的朋友一个参考。
为什么要离开 Typecho
Typecho 本身是个很优秀的轻量博客系统——PHP 编写、体积小、插件生态也算丰富。但用得越久,几个问题就越明显:
- 动态渲染的性能天花板。每次访问都要走一遍 PHP + 数据库查询,虽然 Typecho 已经很轻,但在高并发或低配服务器上还是捉襟见肘。
- 对服务器有硬依赖。必须有一台跑 PHP 和 MySQL 的机器,还得操心环境升级、安全补丁、数据备份。
- 写作和版本管理割裂。文章存在数据库里,改内容要登录后台,没法用 Git 管理历史、没法在本地编辑器里舒舒服服地写。
- 主题定制成本高。想深度定制主题得啃 PHP 模板,前后端逻辑缠在一起。
这些痛点积累到一定程度,我就开始寻找替代方案。
为什么是 Astro
调研了一圈静态站点生成器(Hugo、Hexo、Next.js、Nuxt…),最后选了 Astro,理由很实在:
- 默认零 JS。对内容型博客来说,页面就该是纯静态 HTML,Astro 把”无 JavaScript 输出”做成了默认值,加载速度直接拉满。
- 内容集合(Content Collections)。这是我最喜欢的设计——Markdown 文件就是数据源,配合 Zod schema 做类型校验,写文章就像写代码一样有类型安全感。
- 组件化 + Islands 架构。布局、侧边栏、导航都能拆成可复用组件;需要交互的地方(比如追番分页、主题切换)再按需水合,不会为了一个按钮把整站变成 SPA。
- 和 Tailwind 配合顺畅。现代 CSS 写起来舒服,暗色模式、响应式都很好处理。
迁移过程
第一步:内容导出
Typecho 的文章存在数据库里,我需要把它们变成 Markdown。做法是写一个小的导出脚本,遍历文章表,把标题、正文、分类、标签、发布时间映射出来。正文是 HTML,需要转成 Markdown(可以用 turndown 之类的库),图片则统一收集到本地目录。
这一步最麻烦的是数据清洗——历史文章里总有些残缺的图片链接、过时的嵌入代码,需要逐个过一遍。
第二步:建立内容模型
在 Astro 里,我定义了这样的内容集合:
// src/content.config.ts
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(),
category: z.string().optional(),
tags: z.array(z.string()).optional().default([]),
pinned: z.boolean().optional().default(false),
}),
});
每篇文章就是一个 .md 文件,frontmatter 里放元数据,正文就是纯 Markdown。分类、标签直接从 frontmatter 读取,自动生成分类页和标签页。
第三步:主题重建
这是工作量最大的部分。我把原 Typecho 主题的视觉风格,用 Astro + Tailwind 重新实现了一遍:
- 卡片式布局:文章、相册、侧边栏统一成
rounded-xl shadow-md border一套风格。 - 暗色模式:
class策略 +localStorage持久化,跟随系统或手动切换。 - 保留的功能:除了博客,还有相册、说说、友链、归档,迁移过程中都一一对应重建了。
第四步:部署
静态站点的部署简单太多了。astro build 产出一堆静态文件,丢到任何静态托管(Vercel、GitHub Pages、Cloudflare Pages、又或者对象存储 + CDN)都能跑,再也不用管服务器。
迁移后的一些变化
- 性能:页面从”动态查询”变成”纯静态”,首屏加载肉眼可见地快了。
- 写作体验:Markdown + Git,本地编辑器写完直接推上去,历史可回溯。
- 维护成本:从”维护一台服务器”变成”维护一个仓库”,心智负担小了很多。
踩过的坑
- Astro 大版本升级。迁移过程中赶上了 Astro 5 → 7 的升级,内容集合的配置从
src/content/config.ts迁移到了src/content.config.ts,并且要用 loader 替代旧的type字段。不熟悉的话很容易报LegacyContentConfigError。 - JSON 数组的 schema。相册这类用 JSON 存数据的内容,Astro 会把 JSON 数组自动拆成多个 entry,schema 要写成单个对象
z.object(...)而不是z.array(...),这个坑当时排查了好一会儿。 - 外部图片防盗链。比如 B 站的封面图有防盗链,直接引用会 403,需要下载到本地或者加
referrerpolicy="no-referrer"。 - 中文排版。Markdown 渲染出来的正文,行高、字距要单独调一下,否则中英文混排看起来会有点挤。
写在最后
从 Typecho 到 Astro,本质上是把博客从”一个需要养着的系统”变成了”一组能放进 Git 的文件”。动态博客有动态博客的方便,静态博客有静态博客的自由。如果你也在纠结要不要迁移,我的建议是:先导出内容,搭一个最小可用的 Astro 项目跑起来看看感觉,再决定要不要全量投入。
迁移这件事,最难的永远是迈出第一步。