Home / Blog / Astro
Tech · Astro · Hexo · 运维

从 Hexo 到 Astro:haofly.net 的重构笔记

用 Claude Code 把跑了 10 年的 Hexo 博客重构成 Astro 6,373 篇文章迁移、SEO 保留、评论改 Giscus、Netlify 自动部署、踩坑记录。

H by Haofly
· 2026-04-23

博客从 2013 年开到现在,经历了 GitHub Pages、WordPress、自建 Django、Hexo 一连串折腾。这次换到 Astro,一天内完成,全程用 Claude Code 辅助。

为什么换 Astro

Hexo 用了 10 年,问题也攒了 10 年。373 篇全量构建要 30 秒以上。想改样式得进 themes/next/ 的 ejs,一改就和主题升级冲突。主流插件多年没更新。db.json 有 19 MB,public/ 100 MB。ejs 模板加前端 JS 的组合也不好 debug。

Astro 的吸引点有几个。默认纯静态 HTML,需要交互再加一个 island。Content Collections 能对 frontmatter 做类型检查,还能自动生成 TS 类型。Vite 的增量构建几百毫秒就完事。组件层不锁框架,React、Vue、Svelte 都能塞。

基础设置

技术栈:

Astro 6.1.9 + @astrojs/mdx 5.0.4 + @astrojs/sitemap + @astrojs/rss
Node 22 (engines 锁死)
pnpm 9
TypeScript 严格模式

项目结构:

src/
├── content/
│   ├── posts/          # 所有文章(tech + life 合并)
│   ├── products.yaml   # 产品清单
│   └── ...
├── layouts/
│   ├── BaseLayout.astro
│   └── PostLayout.astro
├── pages/
│   ├── [...slug].astro # 老 URL 保留的 catchall
│   ├── blog/index.astro # 全部文章归档
│   ├── about.astro
│   └── rss.xml.ts
├── config/site.ts      # 站点配置 + 隐身模式 env gate
└── lib/postUrl.ts

内容迁移

写了一个脚本把 Hexo 的 373 篇 markdown 一次性搬到 Astro 的 content collection,统一 frontmatter、图片路径和日期格式。过程中记几条。

Hexo 允许 created: 代替 date:,我多年两个字段混用。脚本起初只读 date:,没有 date: 的文章全被塞了默认日期挤到 2017 年度。修复办法是 date ?? created ?? 默认 三级兜底。

Shiki 的代码语言标签比 Hexo 严格。我过去乱写 reactmysqldjangovuejs 不报错,Astro 全报。用 sed 统一换成 jsxsqlpythonvue

老图片 84 MB 不想进 git。legacy-hexo/source/uploads/ 保留在仓库,public/uploads/.gitignore,构建时 prebuild 钩子复制过去。

有篇 address-favorites.md frontmatter 里嵌了双引号,YAML 解析失败。改成单引号外包、内部不转义就好。

URL 保留(SEO)

老文章的 URL 不能变,否则 Google 索引全部失效。Astro 的做法是在 frontmatter 加 legacyUrl

---
title: Docker 使用手册
legacyUrl: /docker/
---

然后在 src/pages/[...slug].astro 里写 catchall:

export async function getStaticPaths() {
  const posts = await getCollection('posts',
    ({ data }) => !data.draft && !!data.legacyUrl)

  return posts.map((entry) => {
    const slug = entry.data.legacyUrl.replace(/^\/+|\/+$/g, '')
    return { params: { slug }, props: { entry } }
  })
}

legacyUrl 的文章都在原路径渲染。新文章走 /blog/<slug>/

Astro 默认不加尾斜杠,Hexo 加。Netlify 里用 301 统一:

# netlify.toml
[[redirects]]
  from = "/:slug"
  to = "/:slug/"
  status = 301
  force = false

外加 astro.config.mjstrailingSlash: 'always' 保持一致。

评论系统

从 Disqus、Giscus、Utterances、Remark42、Waline、Cusdis、Artalk、Twikoo 里选了 Giscus。理由是零后端(评论存在 GitHub Discussions 里),国内访问走 giscus.app CDN 不经过 GitHub 主站所以不被墙,11.6k 星活跃维护,Markdown 和 reactions 都支持。

一个问题:我的代码仓库是私有的(stealth 模式),但 Giscus 要求评论仓库公开,否则只有 collaborator 能评论。我新建了空仓库 haoflynet-comments,放个 README 说明用途,打开 Discussions,装 Giscus App。评论就沉淀在这里,代码仓库不暴露。

SEO 加固

Astro 给了基础(静态预渲染、sitemap、canonical),中高层需要自己补:

位置作用
robots.txtpublic/robots.txt允许抓取 + sitemap 指向
JSON-LD BlogPostingPostLayout.astro搜索结果富摘要
JSON-LD BreadcrumbList同上面包屑结构化数据
og:type=article + article:published_timeBaseLayout 动态社交卡片
默认 og:image 兜底同上分享不裸奔
robots=noindex(stealth 时)条件渲染防止 dev/预览被索引

Google Fonts 在国内被墙,LCP 直接拖 3 到 5 秒。换成 fonts.loli.net 镜像,一行改完:

<link href="https://fonts.loli.net/css2?family=Inter..." rel="stylesheet" />

部署

Netlify 配置:

# netlify.toml
[build]
  command = "pnpm build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "22"
  PNPM_VERSION = "9"

自定义域名 haofly.net 和 ICP 备案(渝ICP备14004550号-1)都保留。

Secret 防护四层:gitleaks pre-commit 在 commit 前阻断;.env 进 gitignore 放本地 token;Netlify 默认的部署产物扫描;Giscus 等 PUBLIC_ env var 严格校验,生产没设就 fail。

分类换成标签

Hexo 时代的分类体系(backend/frontend/tools/devops/database/languages/platform/ai/life)用了多年,但每篇只打一个分类,tags 几乎没用。Astro 迁移时改成纯标签。分类映射到中文主标签(后端、前端、工具、运维、数据库、生活),再根据文件名关键字自动推断二级标签(Python、Django、Docker、MySQL、Redis、React 等)。一个脚本处理完 373 篇,230 篇只有 1 个标签,143 篇拿到 2 个以上。标签云从 9 个「类别」变成 70 多个可导航标签,长尾更可查。

所用工具

用途工具
AI 辅助Claude Code(全程)
Netlify 管理@netlify/mcp MCP server
Secret 扫描gitleaks
字体fonts.loli.net 镜像
评论Giscus
监控Google Analytics 4
Haofly · 豪翔天下 · 2026-04-23

评论 · Comments

评论由 Giscus 提供,需用 GitHub 账号登录;留言会同步到这个仓库的 Discussions 里。