如何把 Gridea 完整迁移到 Hugo(含踩坑与修复)

发布时间: 2026-02-28 | 标签: gridea hugo migration github-pages gitalk 51la tags redirect slug

最近把博客从 Gridea 迁移到了 Hugo,看起来是“静态博客换框架”这么简单,实际上坑不少。
这篇把整个过程完整记录下来,尤其是那些“迁完才发现”的问题。

一、迁移目标

从这套源目录迁移:

~/GRIDEA_SITES/margrop.gridea

迁移到 Hugo 项目:

~/margrop-blog

要达到的标准:

  1. 文章、图片、标签、slug 全部保留。
  2. 历史 URL 尽量不变(或能兼容跳转)。
  3. 评论(Gitalk)和统计(GA / 51.la)不断档。
  4. 发布流程可自动化,且适配代理网络。

二、内容迁移(文章与 Front Matter)

最早是用一个脚本批量把 Gridea 的 posts/*.md 转成 Hugo 文章(思路正确,后续要补字段):

python3 /root/convert_posts.py

核心动作:

  1. 读取 Gridea front matter:title/date/tags/published/feature
  2. 跳过未发布文章(published: false)。
  3. 写入 Hugo front matter。
  4. <!-- more --> 统一为 Hugo 常见写法 <!--more-->

建议补充点(实战结论):

  1. 一定显式写 slug,避免 URL 漂移。
  2. date 要注意时区与未来时间,否则文章不会被 Hugo 正常发布。
  3. 标签建议保留原始值,但要建立 slug 映射,兼容历史链接。

三、Hugo 路由与基础配置

迁移时先把路由规则定死,不然后续修复成本很高。

hugo.toml 关键项:

[permalinks]
  post = '/post/:slug/'
  tags = '/tag/:slug/'

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

这一步决定了文章和标签最终 URL。

四、静态资源与历史链接兼容

迁移后最容易丢的是这两类资源:

  1. post-images(正文图片)
  2. 历史标签短 ID 链接

处理方式:

  1. 把图片静态目录完整迁入 static/
  2. 保留标签映射文件(例如 static/tag-slug-map.jsstatic/slug-to-name-map.js)。
  3. 为旧短链生成重定向页面:/tag/<旧ID>/ -> /tag/<新slug>/

这样历史外链、搜索引擎收录、微信旧链接都能继续访问。

五、评论系统迁移(Gitalk)

这是最容易“看起来有评论框,实际上全挂”的部分。

1) 配置迁移

把 Gridea 里的 Gitalk 参数迁到 Hugo 数据文件,例如:

{
  "enabled": true,
  "clientID": "...",
  "clientSecret": "...",
  "repo": "margropcomment.margrop.io",
  "owner": "margrop",
  "admin": ["margrop"]
}

2) 两个关键坑

坑一:admin 被渲染成字符串而不是数组。
坑二:id 生成规则改了,导致历史 issue 对不上。

旧站规则是:

id: (location.pathname).substring(0, 49)

新站如果改成 hash(如 sha1(RelPermalink)),历史评论会“消失”。

最终修复策略:

  1. admin 必须输出为真实 JS 数组。
  2. id 与旧站保持一致(路径截断 49)。
  3. 国内网络环境下,Gitalk 资源可优先用 jsdelivr

六、统计脚本迁移(GA + 51.la)

文章迁完、页面正常后,最容易漏的是统计脚本的“初始化段”。

这次实际补齐了三段:

  1. js.users.51.la/...
  2. LA_COLLECT + LA.init(...)
  3. LingQue.Monitor().init(...)

仅迁移第一段 script 标签不够,初始化缺失会导致后台没数据。

七、统一页面头部(导航一致性)

迁移后首页和文章页头部不一致,通常是因为多个模板各自写了一套导航。

修复方案:

  1. 提取公共 partial,例如 partials/header-nav.html
  2. 首页、列表页、文章页、标签页全部引用同一份 partial。
  3. 统一样式,避免后续再分叉。

八、发布与代理网络

发布脚本采用:

bash scripts/deploy_github_pages.sh

配置来自:

config/github-pages.json

并从 gridea/setting.json 读取 token / email(兼容旧流程)。

实战坑点:如果网络访问 GitHub 必须走代理,发布脚本需要自动读取代理配置并导出 http_proxy/https_proxy,否则 git clone / git push 会卡死。

九、迁移后的核验清单(强烈建议逐条执行)

# 1) 本地构建
hugo --cleanDestinationDir

# 2) 线上可用性
curl -I https://blog.margrop.net
curl -I https://blog.margrop.net/post/hello-world/
curl -I https://blog.margrop.net/tag/github/

# 3) 页面源码核对(Gitalk/统计)
curl -sL https://blog.margrop.net/post/hello-world/ | rg "gitalk|LA_COLLECT|LingQue|gtag"

补充说明:

  1. GitHub Pages / CDN 有缓存,刚发布时看到旧页面很常见。
  2. ?v=timestamp 可辅助验证是否已命中新版本。
  3. 如果文章 URL 返回 404,先查 date 是否写成未来时间。

十、结论

Gridea -> Hugo 的“内容迁移”并不难,真正难的是“行为迁移”:

  1. URL 行为(slug、tag、旧短链)
  2. 评论行为(Gitalk 的 id 规则)
  3. 统计行为(脚本 + 初始化)
  4. 发布行为(代理、自动化、缓存验证)

把这四块做完整,才算真正的“完整迁移”。

参考