从 WordPress 到 Astro:一次静态博客迁移的过程、取舍与技巧

前几天完成了一次 WordPress 博客到 Astro 静态博客的迁移。背景是博客更新没这么频繁,而且很多动态能力不需要了,开了评论,结果后台几百个各类广告的评论没有任何意义了。博客内容本身不多,就是特别要注意旧链接和旧资源要依然可靠地工作,不然SEO就全清空了。

这篇文章记录迁移过程中的关键判断、实现方式和一些容易被忽略的细节。新站以 Astro 7 和 Tailwind CSS 4 为基础;使用了 Codex + GPT-5.6-terra 作为协作工具。

迁移目标:不是重做网站,而是保住内容资产

从动态博客迁移到静态博客,最容易产生的误解是:导出文章、换一个主题、部署完成,就算迁移成功。

实际上,内容站迁移至少要同时满足几件事:

  • 已发布文章能够继续访问;
  • 历史链接尽量不变,尤其是来自搜索引擎、社交平台和其他文章的链接;
  • 图片、表格、引用和代码等正文内容不出现明显损坏;
  • 新站的维护方式比旧站更简单,特别是静态站点,生成托管就结束了;
  • 视觉风格可以更新,但不应以牺牲阅读体验为代价。

因此,这次迁移的目标并不是复刻旧主题,也不是把 WordPress 的全部功能搬到静态站,而是建立一个内容优先、生成结果稳定、后续维护成本更低的博客。

第一步:先审计导出数据,再决定迁移范围

先从 WordPress 的后台,tools > export 导出全部数据的 xml 。
然后再把 wp-content 文件夹原封不动的复制到 astro 的 public 文件夹下。

WordPress 导出文件看起来像是一份内容备份,但它实际混合了多种记录:文章、页面、附件、草稿、评论、导航数据,以及主题或插件写入的元信息。

如果不先分类,后面很容易把无用数据也带进新站,或者漏掉真正需要保留的内容。我的处理方式是先回答四个问题:

  1. 哪些是已经公开发布的文章?
  2. 哪些页面仍然需要保留访问入口?
  3. 正文引用了哪些上传资源,它们是否已在本地保存?
  4. 哪些内容不适合、也没有必要迁移?

最终,迁移范围被限定为已发布文章和一个必要的兼容页面。评论、评论元数据、WordPress 后台、附件详情页、搜索、订阅源、主题模板记录和未发布草稿均不迁移。

这个“先决定不迁移什么”的步骤很重要。静态站不应机械复制动态系统,而应只保留真正有价值、可长期维护的部分。

第二步:把历史 URL 当作迁移契约

内容迁移中最有价值的资产,除了文章本身,就是既有 URL。

旧文章使用的是按日期组织的永久链接:

1
/YYYY/MM/post-slug/

但是如果是使用query string也就是 ?slug=xxx 的就很难办了,静态托管不太支持这种形式的链接,好在默认的是用 path 区分的。

新站继续生成完全相同的路径,而不是改成更短的 /post-slug/。后者看似更简洁,却会让已经被收录、转发或收藏的链接全部失效,并把大量本可避免的 404 留给读者和搜索引擎。

在 Astro 中,这种路径可以通过动态页面路由实现。文章元数据中保留发布日期和 slug,在构建阶段生成对应的年、月、slug 参数;最终输出仍是静态 HTML,却能稳定覆盖每一篇历史文章。

路径:

1
src\pages\[year]\[month]\[slug].astro

获取静态路径的函数:

1
2
3
4
5
6
7
8
9
10
11
export async function getStaticPaths() {
const posts = await getCollection("posts");
return posts.map((post) => ({
params: {
year: post.data.year,
month: post.data.month,
slug: post.data.slug,
},
props: { post },
}));
}

这里还有两个细节:

  • 输出形式要兼容结尾斜杠;部署平台、静态站生成设置和站内链接应保持一致。
  • ?p=123 这类旧式查询参数链接不能由纯静态页面天然接住,是否保留要看部署平台是否支持重定向规则。它的优先级通常低于公开文章的永久链接,但上线前仍应单独审计。

页面可以改版,URL 尽量不要改。

第三步:从 Gutenberg HTML 到可维护的 Markdown

WordPress 的正文常常来自 Gutenberg 编辑器。导出后的内容虽然是 HTML,但里面往往夹杂区块注释、展示性类名、内联样式和编辑器遗留结构。

迁移时的原则是保留语义,而不是保留原始标签结构。

需要保留的通常包括:

  • 标题、段落和强调;
  • 超链接与图片;
  • 有序或无序列表;
  • 引用块、代码块;
  • 对比表格等承载信息的结构。

而 WordPress 区块注释、纯展示用途的 class、编辑器生成的空容器,以及主题相关的样式说明,都应该去掉。它们在新站没有意义,反而会让 Markdown 难读、难维护。

尤其要留意表格。普通段落转 Markdown 通常比较顺利,但表格、嵌入内容和含有转义 CSS 的文章很容易出现格式残留。与其追求一次自动转换全部正确,不如先完成批量转换,再进行针对性人工复查。重点检查:

  • 表格的列数和表头是否正确;
  • 代码块是否被错误拆分;
  • 图片链接是否仍然有效;
  • HTML 实体和转义字符是否污染正文;
  • 长文章的标题层级是否合理。

迁移完成后,清理过的 Markdown 应当成为仓库里的正式内容源。一次性的导入脚本可以不保留;真正重要的是让以后编辑文章时,不必重新理解旧 CMS 的导出格式。

第四步:图片不急着“优化”,先确保不丢

图片往往是迁移中最容易被低估的部分。文章正文能渲染,不代表资源一定齐全;一旦原站下线或上传目录路径发生变化,历史文章中的图片就会全部变成断链。

这次采用的策略很朴素:保留原有上传目录结构,将文章中的图片路径继续指向本地静态资源。例如:

1
/wp-content/uploads/2024/01/example-image.png

只要同样的文件存在于静态站的 public/wp-content/uploads 下,构建后的访问路径就能保持不变。

这有几个好处:

  • 不需要逐篇修改图片 URL;
  • 不会因为文件改名导致旧内容失效;
  • 可以先完成可靠迁移,再选择性做 WebP、压缩、尺寸优化或图片组件改造;
  • 导入流程不会移动、重命名或修改原始资源,降低误操作风险。

迁移时建议把“文章中引用到的唯一图片 URL”与本地资源目录做一次逐项比对。构建成功只说明代码没有报错,不代表每一张图片都存在。

第五步:用内容集合约束文章元数据

Astro 的内容集合适合承接 Markdown 博客。相比把文章文件当作完全自由的文本,它可以为 Front Matter 建立 Schema,让标题、日期、slug、摘要、标签等字段在构建时得到校验。

例如,一篇文章至少应有标题和发布日期,实际使用的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "astro/zod";

const posts = defineCollection({
loader: glob({
base: "./src/content/posts",
pattern: "**/*.md",
}),
schema: z.object({
title: z.string(),
description: z.string(),
publishedAt: z.string(),
year: z.string().regex(/^\d{4}$/),
month: z.string().regex(/^\d{2}$/),
slug: z.string(),
categories: z.array(z.string()),
tags: z.array(z.string()),
}),
});

export const collections = { posts };

对应的 md 的frontmatter:

1
2
3
4
5
6
7
8
9
10
11
12
13
---
title: "Title"
description: "Description"
publishedAt: "2026-08-01 12:06:44"
year: "2026"
month: "08"
slug: "Slug"
categories: ["guides"]
tags: ["codex"]
---

内容

这种看似简单的约束,能尽早发现日期格式错误、漏填标题、标签结构不一致等问题。对于需要按年月生成路由、按日期排序、生成首页归档和 sitemap 的静态博客来说,内容元数据就是整个站点的基础数据层。

第六步:视觉重做应服务于阅读

迁移是重新设计信息层级的机会,但不是给页面堆更多装饰的理由。

新站采用了克制的单色编辑式视觉:浅色背景、深色文字、细规则、明确的排版层级,以及较少的元信息干扰。首页以归档和文章索引为主,文章页则让正文占据视觉中心。

Tailwind CSS 4 很适合这类工作。它把颜色、间距、断点和排版规则集中在一套可组合的样式体系中,既能快速完成页面,也便于后续逐渐收敛为稳定的设计令牌。

如果要加入粒子、网格等氛围装饰,也应遵守几个边界:

  • 仅作为背景,不阻塞阅读或点击;
  • 密度和对比度保持很低;
  • 不要在所有页面重复出现;
  • prefers-reduced-motion 提供静态效果或直接关闭动效。

好的博客视觉通常不会抢走文章的注意力,而是在读者没有察觉时让阅读更顺畅。

第七步:不要遗漏分页、404 和兼容页面

静态站常见的遗漏不在文章详情页,而在边缘页面。

首页需要稳定的排序和分页策略。如果旧站已经存在 /page/2/ 一类的访问入口,最好一起生成对应的静态兼容页面。即使首页的文章数量不多,也应考虑它未来增长后的行为,而不是只为当前数据量设计。

同样需要补齐的还有:

  • 404 页面;
  • 旧占位页面的替代内容;
  • 站内导航和页脚链接;
  • canonical URL;
  • sitemap;
  • 部署平台支持的重定向配置。

这些页面和配置平时不显眼,却直接决定迁移后的完整度,也影响搜索引擎对新站结构的理解。

Codex + GPT-5.6-terra 在这次迁移中做了什么

这次没有把 AI 当成“点一下就自动完成迁移”的工具,而是把 Codex + GPT-5.6-terra 放在适合它的位置:处理重复劳动、协助建立检查框架,以及加快局部实现。

它尤其适合协助完成以下工作:

  • 阅读导出说明并整理迁移范围;
  • 归纳内容字段、路由规则和资产清单;
  • 生成 Astro 内容集合、动态路由和分页的初始实现;
  • 对 HTML 转 Markdown 后的残留内容进行模式识别;
  • 编写构建前检查清单和人工验收项;
  • 在已有设计方向下协助搭建组件和 Tailwind 样式。

但有几件事仍然必须人工确认:

  • 每一篇公开文章是否对应正确的历史 URL;
  • 图片和表格是否在语义与视觉上都正确;
  • 是否错误迁入草稿、评论或敏感元数据;
  • 站点文案和视觉是否符合预期;
  • 生产构建、部署规则和真实访问结果是否一致。

AI 很擅长把模糊任务拆成可执行步骤,也很擅长处理大量重复格式;但迁移的最终责任仍在于人对内容、兼容性和发布结果的验收。

上线前检查清单

最后,给这类迁移留一份简单但实用的检查清单:

  • 所有已发布文章均已迁入,草稿没有被发布;
  • 历史文章 URL 的年、月、slug 和结尾斜杠符合旧链接;
  • 文章内图片、链接、表格、代码块和引用显示正常;
  • 首页排序、分页和兼容分页路径正常;
  • 404 与必要兼容页面已经提供;
  • canonical、sitemap 和基础 SEO 信息已配置;
  • 生产构建通过;
  • 在部署后的真实域名环境中抽查访问与资源加载;
  • 根据托管平台能力,为低优先级旧链接补充重定向。

总结

这次迁移最重要的收获是:静态化不只是换一个框架,而是一次重新梳理内容边界和长期维护方式的机会。

保住 URL,保住内容语义,保住资源路径;把不再需要的动态功能留在过去;再用一套足够轻、足够明确的内容模型和页面结构承接未来更新。