背景

我的博客建于 2014 年,最初使用 WordPress 搭建。十年来,积累了 282 篇文章,涉及技术笔记、产品评测、行业观察等多个类别。

但随着时间推移,WordPress 的问题越来越明显:

  • 性能瓶颈:每次访问都要查询数据库,响应慢,需要缓存插件维持
  • 维护负担:核心、插件、主题需要频繁更新,安全隐患多
  • 资源消耗:PHP + MySQL 的组合对服务器资源要求较高
  • 备份复杂:需要同时备份数据库和文件,恢复流程繁琐

2026 年初,我决定将博客迁移到 Hugo —— 一个基于 Go 语言的静态网站生成器。

迁移完成后,博客的访问速度提升了 5-10 倍,服务器资源占用降低了 90% 以上

本文记录了整个迁移过程,希望对有类似需求的朋友有所帮助。


一、为什么选择 Hugo?

在决定迁移之前,我对比了几种主流的静态网站生成器:

工具优点缺点
Hugo极快(毫秒级构建)、Go 原生、主题丰富模板语法需要学习
JekyllGitHub 原生支持、生态成熟Ruby 依赖、构建较慢
Hexo中文社区活跃、上手简单Node.js 依赖、插件质量参差
Astro现代化、支持多框架相对较新、迁移资源少

最终选择 Hugo 的原因

  1. 构建速度:282 篇文章的构建时间不到 5 秒
  2. 单文件部署:Hugo 是一个独立二进制文件,无需安装依赖
  3. 主题生态:PaperMod、Stack 等主题现代且维护活跃
  4. 迁移工具:社区有成熟的 WordPress 转 Hugo 方案

二、迁移方案设计

2.1 迁移流程概览

WordPress (MySQL)
导出 WXR XML(WordPress 导出工具)
PHP 转换脚本(自定义 wp2hugo.php)
Hugo Markdown 文件
Hugo 构建
静态 HTML(Nginx 托管)

2.2 关键决策

决策一:使用 PHP 转换而非现成工具

社区有现成的 WordPress to Hugo 导入工具(如 wordpress-to-hugo-exporter 插件),但我选择自己写 PHP 脚本,原因:

  • 更灵活地处理中文标题和标签
  • 可以自定义 front matter 格式
  • 对数据有更精细的控制

决策二:保留原有 URL 结构

为了 SEO,迁移后的文章 URL 需要与原 WordPress 保持一致。WordPress 的 URL 格式是 /年份/月份/文章名/,Hugo 默认是 /posts/文件名/

最终选择折中方案:按年份分目录,文件名使用 post slug。

决策三:文章内容保持 HTML

WordPress 文章内容是 HTML 格式。虽然有工具可以转换为 Markdown,但:

  • 10 年积累的 HTML 格式复杂,转换容易出错
  • Hugo 的 Goldmark 渲染器支持在 Markdown 中嵌入 HTML
  • 保留 HTML 更安全,避免格式丢失

在 Hugo 配置中启用 HTML 支持:

[markup]
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = true  # 允许 HTML

三、迁移实施

3.1 导出 WordPress 数据

WordPress 后台 → 工具 → 导出 → 选择"所有内容",导出 WXR 格式的 XML 文件。

我的导出文件约 3MB,包含所有文章、页面、分类、标签和评论(评论未迁移)。

3.2 编写转换脚本

这是迁移的核心步骤。我写了一个 PHP 脚本 wp2hugo.php

<?php
$xmlFile = '/path/to/wordpress-export.xml';
$outputDir = '/path/to/hugo/content/posts';

$xml = simplexml_load_file($xmlFile);
$namespaces = $xml->getNamespaces(true);

function yaml_escape($str) {
    $str = (string)$str;
    $str = str_replace('\\', '\\\\', $str);
    $str = str_replace('"', '\\"', $str);
    $str = str_replace("\n", '\\n', $str);
    return '"' . $str . '"';
}

$count = 0;
foreach ($xml->channel->item as $item) {
    $wp = $item->children($namespaces['wp']);
    $post_type = (string)$wp->post_type;
    $status = (string)$wp->status;
    
    // 只处理已发布的文章
    if ($post_type !== 'post' || $status !== 'publish') continue;
    
    $title = trim((string)$item->title);
    $pubDate = date('Y-m-d', strtotime((string)$item->pubDate));
    $slug = (string)$wp->post_name;
    $body = (string)$item->children($namespaces['content'])->encoded;
    
    // 提取分类和标签
    $categories = [];
    $tags = [];
    foreach ($item->category as $cat) {
        $domain = (string)$cat->attributes()['domain'];
        $name = trim((string)$cat);
        if ($domain === 'category') $categories[] = $name;
        elseif ($domain === 'post_tag') $tags[] = $name;
    }
    
    // 生成 Hugo front matter
    $fm = "---\n";
    $fm .= "title: " . yaml_escape($title) . "\n";
    $fm .= "date: " . $pubDate . "T00:00:00+08:00\n";
    $fm .= "draft: false\n";
    
    if ($categories) {
        $fm .= "categories:\n";
        foreach ($categories as $c) $fm .= "  - " . yaml_escape($c) . "\n";
    }
    if ($tags) {
        $fm .= "tags:\n";
        foreach ($tags as $t) $fm .= "  - " . yaml_escape($t) . "\n";
    }
    $fm .= "---\n\n";
    
    // 按年份分目录保存
    $yearDir = $outputDir . '/' . substr($pubDate, 0, 4);
    if (!is_dir($yearDir)) mkdir($yearDir, 0755, true);
    
    $filename = $yearDir . '/' . preg_replace('/[^a-z0-9-]/i', '-', $slug) . '.md';
    file_put_contents($filename, $fm . $body);
    $count++;
}
echo "Converted: $count posts\n";

脚本要点

  • 使用 PHP 的 SimpleXML 解析 WXR 格式
  • 正确处理 WordPress 命名空间(wp: 前缀)
  • 对中文标题进行 YAML 转义
  • 按发布年份创建子目录

运行脚本:

php wp2hugo.php
# 输出: Converted: 282 posts

3.3 配置 Hugo

创建 config.toml

baseURL = 'https://zhaoyanblog.com/'
languageCode = 'zh-cn'
defaultContentLanguage = 'zh-cn'
title = '赵岩的技术笔记'
theme = 'PaperMod'

enableRobotsTXT = true

[pagination]
pagerSize = 15

[params]
env = 'production'
description = '记录学习、工作和思考'
defaultTheme = 'auto'
ShowReadingTime = true
ShowCodeCopyButtons = true
ShowBreadCrumbs = true

[taxonomies]
category = 'categories'
tag = 'tags'

[outputs]
home = ['HTML', 'RSS', 'JSON']

[markup]
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = true  # 允许 HTML
[markup.highlight]
codeFences = true
guessSyntax = true
style = 'github'

3.4 安装主题

cd /path/to/hugo
git clone https://github.com/adityatelange/hugo-PaperMod themes/PaperMod --depth=1

3.5 构建和部署

创建 build.sh

#!/bin/bash
cd /home/zhaoyan/zhaoyanblog/hugo
/usr/local/bin/hugo "$@"
cp public/index.json public/page/ 2>/dev/null

Nginx 配置:

server {
    listen 443 ssl;
    server_name zhaoyanblog.com;
    
    ssl_certificate   cert/zhaoyanblog.com.pem;
    ssl_certificate_key  cert/zhaoyanblog.com.key;
    
    root /home/zhaoyan/zhaoyanblog/hugo/public;
    index index.html;
    
    location / {
        try_files $uri $uri/ =404;
    }
}

四、遇到的问题与解决

问题一:YAML 格式错误

WordPress 文章标题中包含引号、冒号等特殊字符,直接写入 YAML front matter 会导致解析错误。

解决方案:在 PHP 脚本中实现 yaml_escape() 函数,对特殊字符进行转义:

function yaml_escape($str) {
    $str = str_replace('\\', '\\\\', $str);
    $str = str_replace('"', '\\"', $str);
    $str = str_replace("\n", '\\n', $str);
    return '"' . $str . '"';
}

问题二:代码高亮失效

WordPress 使用 WP-Syntax 等插件实现代码高亮,迁移后代码块变成普通文本。

解决方案:Hugo 内置 Chroma 代码高亮,但需要调整配置:

[markup.highlight]
codeFences = true
guessSyntax = true  # 自动检测语言
style = 'github'

对于旧的 <pre lang="php"> 格式,需要手动转换为 Markdown 代码块:

```php
// 代码内容
```

问题三:图片路径问题

WordPress 的图片存储在 /wp-content/uploads/ 目录,迁移后路径失效。

解决方案

  1. 将 WordPress 的 wp-content/uploads/ 目录复制到 Hugo 的 static/wp-content/uploads/
  2. 或者修改文章中的图片路径为新的静态资源路径

问题四:URL 重定向

WordPress 原有的 URL 结构(如 /2024/01/hello-world/)与 Hugo 不同,可能导致 SEO 损失。

解决方案:在 Nginx 中添加重定向规则,或使用 Hugo 的 aliases 功能:

---
title: "文章标题"
aliases:
  - /2024/01/hello-world/
---

五、迁移效果

5.1 性能对比

指标WordPressHugo提升
首页加载时间1.2s0.15s8x
文章页加载时间0.8s0.1s8x
服务器内存占用400MB+<50MB8x+
构建时间-4.8s282篇文章

5.2 文章统计

总文章数:282
年份分布:
  2014: 146 篇
  2015: 82 篇
  2016: 21 篇
  2017: 14 篇
  2018: 8 篇
  2019: 4 篇
  2020: 3 篇
  2025: 1 篇
  2026: 3 篇(含本文)

5.3 维护简化

迁移前(WordPress)

  • 每月更新 WordPress 核心
  • 定期更新插件和主题
  • 监控数据库性能
  • 维护缓存插件

迁移后(Hugo)

  • 写 Markdown → hugo → 部署
  • 偶尔更新 Hugo 版本
  • 无数据库维护
  • 无缓存需求

六、总结与建议

6.1 迁移是否值得?

如果你满足以下条件,迁移是值得的:

  • 文章数量在 1000 篇以内
  • 对评论功能需求不高(或可使用第三方评论如 Giscus)
  • 希望降低服务器成本
  • 追求极致的访问速度
  • 愿意投入时间处理迁移细节

如果你依赖以下 WordPress 特性,可能需要再考虑:

  • 复杂的插件生态(会员、电商等)
  • 大量用户互动(评论、社区)
  • 多作者协作
  • 可视化编辑器(Gutenberg)

6.2 迁移建议

1. 充分测试

在正式切换前,先在测试环境验证迁移结果。检查:

  • 所有文章能否正常访问
  • 图片、链接是否有效
  • 搜索功能是否正常
  • RSS 订阅是否可用

2. 保留回退能力

迁移完成后,保留 WordPress 的数据库和文件备份至少一个月。如果发现问题,可以快速回退。

3. 逐步优化

迁移后不要急于删除 WordPress。先稳定运行一段时间,再逐步:

  • 清理无效的 HTML 标签
  • 优化图片资源
  • 更新文章内容

4. 利用 AI 辅助

本次迁移过程中,我使用了 OpenClaw(AI 助手)来:

  • 分析 WordPress 导出数据结构
  • 生成和调试 PHP 转换脚本
  • 编写 Hugo 配置文件
  • 排查部署问题

AI 可以显著加速迁移过程,尤其是处理重复性工作时。


七、后续计划

迁移完成不是终点,而是新的起点。后续计划:

  1. 主题定制:基于 PaperMod 进行个性化定制
  2. SEO 优化:完善 meta 标签、sitemap、结构化数据
  3. 评论系统:集成 Giscus(基于 GitHub Discussions)
  4. 搜索增强:利用 Hugo 的 JSON 输出实现全文搜索

参考资料

  1. Hugo 官方文档
  2. WordPress 导出格式说明
  3. PaperMod 主题文档
  4. Hugo 从 WordPress 迁移指南

本文写于 2026 年 2 月 22 日。感谢 OpenClaw 在迁移过程中提供的协助。