从 Typecho 迁移到 blog-v3:一次把博客搬进 Nuxt Content 的完整记录

记录从 Typecho 迁移到 blog-v3 / Clarity 的完整过程,包括备份、文章导出、Markdown 转换、图片迁移、链接保持、评论处理、本地预览和 Vercel 部署。

写在前面

用了很久 Typecho 之后,我最终还是把博客迁移到了基于 Nuxt 4 和 Nuxt Content 的 blog-v3

Typecho 很轻、很稳,也足够适合个人博客。它的优势很明显:部署简单、后台好用、插件丰富、服务器要求低。但随着博客内容越来越多,我对博客系统的需求也慢慢变了:

  • 想把文章变成 Git 仓库里可管理的 Markdown 文件;
  • 想用 VS Code / Cursor / 任意编辑器写文章,而不是登录后台;
  • 想把主题、组件、页面、样式都放进同一个工程里维护;
  • 想接入现代前端生态,比如 Nuxt、Vue、Vercel、自动构建;
  • 想让博客部署更接近“提交即发布”的工作流。

于是就有了这次迁移。

这篇文章不是单纯的“我换了个主题”,而是一次从传统 PHP + 数据库博客,迁移到 Markdown + Git + Nuxt Content 静态/服务端渲染博客的完整记录。下面会尽量详细写清楚每一步,包括备份、导出、转换、图片处理、链接保持、评论迁移和部署上线。

迁移前后架构对比

先看一下迁移前后的差异。

Typecho 时代

Typecho 的典型结构大概是这样:

txt
Nginx / Apache
  └─ PHP
      └─ Typecho
          ├─ MySQL / SQLite 数据库
          ├─ usr/themes/主题
          ├─ usr/plugins/插件
          └─ usr/uploads/附件

文章、页面、评论、分类、标签基本都在数据库里。图片等附件在 usr/uploads 目录。主题和插件则在 usr/themesusr/plugins

这种架构的优点是成熟稳定,后台操作方便;缺点是内容和程序耦合较深,迁移、版本管理、批量修改都不如纯文本文件直观。

blog-v3 时代

迁移后的结构更接近现代前端博客:

txt
blog-v3
├─ content/
│  ├─ posts/
│  │  └─ 2026/
│  │     └─ typecho-to-blog-v3.md
│  └─ link.md
├─ app/
│  ├─ pages/
│  ├─ components/
│  └─ app.config.ts
├─ blog.config.ts
├─ nuxt.config.ts
└─ package.json

文章直接放在 content/posts/年份/ 下面,每篇文章就是一个 Markdown 文件,顶部用 Front matter 写标题、日期、分类、标签等元信息。

比如本文的文章头部就是这样:

yaml
---
title: 从 Typecho 迁移到 blog-v3:一次把博客搬进 Nuxt Content 的完整记录
description: 记录从 Typecho 迁移到 blog-v3 / Clarity 的完整过程,包括备份、文章导出、Markdown 转换、图片迁移、链接保持、评论处理、本地预览和 Vercel 部署。
date: 2026-05-19 12:45:00
updated: 2026-05-19 12:45:00
categories: [技术]
tags: [Typecho, Nuxt, Nuxt Content, 博客迁移, Vercel]
---

从此以后,文章就是代码仓库的一部分,可以用 Git 记录变更,也可以通过 Vercel 自动构建发布。

第一步:迁移前一定要完整备份

迁移博客之前,最重要的不是转换工具,而是备份。

建议至少备份三类东西:

  1. 数据库;
  2. 附件目录;
  3. Typecho 配置、主题和插件。

备份数据库

如果你使用的是 MySQL,可以在服务器上执行:

bash
mysqldump -u 数据库用户 -p 数据库名 > typecho-backup.sql

例如:

bash
mysqldump -u typecho -p typecho > typecho-2026-05-19.sql

如果你不确定数据库名,可以查看 Typecho 根目录下的 config.inc.php

php
$db = new Typecho_Db('Mysql', 'typecho_');
$db->addServer(array (
  'host' => 'localhost',
  'user' => 'typecho',
  'password' => 'password',
  'charset' => 'utf8mb4',
  'port' => '3306',
  'database' => 'typecho',
), Typecho_Db::READ | Typecho_Db::WRITE);

里面的 database 就是数据库名,prefix 通常是 typecho_

如果使用 SQLite,则一般只需要备份对应的 .db 文件。

备份附件目录

Typecho 的图片和附件通常在:

txt
usr/uploads/

可以打包:

bash
tar -czf typecho-uploads.tar.gz usr/uploads

也可以直接用 rsync 下载到本地:

bash
rsync -avz root@你的服务器:/网站目录/usr/uploads ./typecho-uploads

备份主题和插件

虽然这次迁移到 blog-v3 后,Typecho 主题和插件基本不会继续使用,但它们里面可能有一些自定义代码、统计脚本、友链配置、备案信息、导航链接等内容。

建议一起备份:

bash
tar -czf typecho-theme-plugin.tar.gz usr/themes usr/plugins config.inc.php

备份完成后,再开始动手迁移,这样即使中间转换失败,也随时可以回滚。

第二步:准备 blog-v3 项目

如果还没有 blog-v3,可以先克隆项目:

bash
git clone https://gh-proxy.com/https://github.com/L33Z22L11/blog-v3.git
cd blog-v3

安装依赖:

bash
pnpm install

本地启动:

bash
pnpm dev

默认情况下,文章内容放在:

txt
content/posts/年份/

例如:

txt
content/posts/2026/hello-world.md

blog-v3 使用 blog.config.ts 管理站点基础信息,比如站点标题、作者、邮箱、站点地址、分类、文章类型等。迁移前建议先把这些基础配置改好。

例如:

ts
const basicConfig = {
  title: '心记|Mood',
  subtitle: '繁华已尽,空散云烟',
  description: '繁华已尽,空散云烟',
  author: {
    name: 'MoodLog',
    avatar: 'https://q1.qlogo.cn/g?b=qq&nk=80295940&s=640',
    email: 'admin@moodlog.cn',
    homepage: 'https://blog.moodlog.cn/',
  },
  url: 'https://blog.moodlog.cn/',
  timeZone: 'Asia/Shanghai',
  defaultCategory: '未分类',
}

这里建议优先确认三项:

  • title:站点名称;
  • url:正式域名;
  • timeZone:时区,国内站点一般用 Asia/Shanghai

第三步:从 Typecho 导出文章

Typecho 文章主要存储在数据库的 contents 表中,分类和标签关系在 relationshipsmetas 等表中。

如果只是少量文章,可以手动复制。但如果文章比较多,建议写脚本批量导出。

方式一:通过数据库导出

先确认表名。Typecho 默认表前缀一般是 typecho_,文章表通常叫:

txt
typecho_contents

可以查询文章:

sql
SELECT cid, title, slug, created, modified, text, type, status
FROM typecho_contents
WHERE type = 'post' AND status = 'publish'
ORDER BY created DESC;

其中几个字段很关键:

字段说明
cid内容 ID
title文章标题
slug文章缩略名,常用于固定链接
created创建时间,Unix 时间戳
modified修改时间,Unix 时间戳
text正文内容
typepost 是文章,page 是独立页面
statuspublish 是已发布

如果要导出分类和标签,还需要关联 relationshipsmetas 表。

一个常见查询大概是:

sql
SELECT
  c.cid,
  c.title,
  c.slug,
  c.created,
  c.modified,
  c.text,
  GROUP_CONCAT(m.name) AS metas
FROM typecho_contents c
LEFT JOIN typecho_relationships r ON c.cid = r.cid
LEFT JOIN typecho_metas m ON r.mid = m.mid
WHERE c.type = 'post' AND c.status = 'publish'
GROUP BY c.cid
ORDER BY c.created DESC;

不过 Typecho 的 metas 同时包含分类和标签,如果要区分,可以加上 m.type

sql
SELECT
  c.cid,
  c.title,
  c.slug,
  c.created,
  c.modified,
  c.text,
  GROUP_CONCAT(CASE WHEN m.type = 'category' THEN m.name END) AS categories,
  GROUP_CONCAT(CASE WHEN m.type = 'tag' THEN m.name END) AS tags
FROM typecho_contents c
LEFT JOIN typecho_relationships r ON c.cid = r.cid
LEFT JOIN typecho_metas m ON r.mid = m.mid
WHERE c.type = 'post' AND c.status = 'publish'
GROUP BY c.cid
ORDER BY c.created DESC;

方式二:通过 RSS 导出

如果你不方便直接访问数据库,也可以通过 RSS 获取文章列表。

Typecho 默认 RSS 地址可能是:

txt
https://你的域名/feed/
https://你的域名/feed/rss/
https://你的域名/feed/atom/

RSS 的优点是简单、安全,不用碰数据库;缺点是可能只导出最近几十篇文章,且分类、标签、原始 Markdown 信息不一定完整。

因此,完整迁移更推荐数据库导出。

第四步:把 Typecho 内容转换成 Markdown

Typecho 文章正文的格式取决于你当时用的编辑器:

  • 如果原来就是 Markdown,迁移会非常轻松;
  • 如果是 HTML,需要转换成 Markdown;
  • 如果混合了短代码、插件语法、图片懒加载标签,就需要额外清洗。

blog-v3 文章格式

blog-v3 里一篇文章的基本格式如下:

md
---
title: 文章标题
description: 文章摘要
date: 2026-05-19 12:00:00
updated: 2026-05-19 12:00:00
categories: [技术]
tags: [Typecho, Nuxt]
---

## 正文标题

这里是正文内容。

需要注意:

  • date 建议使用 YYYY-MM-DD HH:mm:ss
  • updated 可以使用 Typecho 的修改时间;
  • categoriestags 使用数组;
  • 文件名建议使用英文、拼音或原来的 slug
  • 如果想保持旧链接,可以使用 permalink 字段。

例如:

yaml
permalink: /archives/typecho-to-blog-v3/

这样可以尽量减少搜索引擎和外部链接的损失。

一个简单的转换脚本思路

假设我们已经把 Typecho 数据库导出成 JSON,例如 typecho-posts.json

json
[
  {
    "title": "第一篇文章",
    "slug": "first-post",
    "created": 1710000000,
    "modified": 1710003600,
    "text": "## Hello\n\n正文内容",
    "categories": "技术",
    "tags": "Typecho,博客"
  }
]

可以写一个 Node.js 脚本转换:

这只是一个基础版本,实际迁移时还可以继续增强:

  • 自动把 HTML 转 Markdown;
  • 自动下载远程图片;
  • 自动替换旧附件路径;
  • 自动修复标题层级;
  • 自动移除 Typecho 插件短代码;
  • 自动生成 description

HTML 转 Markdown

如果 Typecho 文章里主要是 HTML,可以使用 turndown 转换:

bash
pnpm add -D turndown

示例:

js
import TurndownService from 'turndown'

const turndown = new TurndownService({
  headingStyle: 'atx',
  codeBlockStyle: 'fenced',
})

const markdownBody = turndown.turndown(htmlBody)

转换完后不要急着发布,最好抽样检查几篇文章,尤其是:

  • 代码块是否完整;
  • 图片是否正常;
  • 表格是否错乱;
  • 引用块是否丢失;
  • 链接是否被错误转义。

第五步:迁移图片和附件

文章迁移最容易踩坑的地方不是正文,而是图片。

Typecho 的图片链接常见形式有:

txt
/usr/uploads/2024/01/example.png
https://example.com/usr/uploads/2024/01/example.png

迁移到 blog-v3 后,有几种处理方式。

方案一:继续使用原图片地址

如果旧站点域名不变,或者你还保留原来的 /usr/uploads/ 路径,可以不改图片链接。

优点:

  • 最省事;
  • 不需要批量替换;
  • 老文章几乎不用动。

缺点:

  • 依赖旧服务器或旧路径;
  • 后续迁移 CDN 时还要再处理;
  • 如果旧目录删除,图片会全部失效。

方案二:把图片放到 public 目录

可以把 Typecho 的 usr/uploads 拷贝到 blog-v3 的 public/uploads

bash
mkdir -p public/uploads
cp -r typecho-uploads/* public/uploads/

然后把文章里的路径从:

md
![](/usr/uploads/2024/01/a.png)

替换成:

md
![](/uploads/2024/01/a.png)

可以用脚本批量替换:

js
import fs from 'node:fs'
import path from 'node:path'

function walk(dir) {
  return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
    const full = path.join(dir, entry.name)
    return entry.isDirectory() ? walk(full) : [full]
  })
}

for (const file of walk('content/posts')) {
  if (!file.endsWith('.md')) continue
  const old = fs.readFileSync(file, 'utf8')
  const next = old.replaceAll('/usr/uploads/', '/uploads/')
  if (next !== old) fs.writeFileSync(file, next, 'utf8')
}

方案三:使用对象存储或图床

如果图片很多,或者希望访问更快,可以把图片上传到对象存储/CDN,例如:

  • S3;
  • R2;
  • OSS;
  • COS;
  • 又拍云;
  • 自建静态资源域名。

然后把文章里的图片统一替换为 CDN 地址。

这种方案更适合长期维护,但第一次配置会稍微麻烦一些。

第六步:处理旧链接和 SEO

迁移博客时,最容易被忽略的是旧链接。

如果搜索引擎已经收录了原来的 Typecho 地址,例如:

txt
https://blog.example.com/archives/123/
https://blog.example.com/archives/typecho-slug/

迁移后如果直接变成:

txt
https://blog.example.com/posts/2026/typecho-slug

那么旧链接可能会 404。

blog-v3 的文章支持 permalink 字段,可以给文章指定自定义链接。

例如:

yaml
---
title: 老文章标题
date: 2024-01-01 12:00:00
permalink: /archives/old-slug/
---

这样访问旧路径时依然能打开文章。

迁移时建议尽量保留 Typecho 原来的 slug,并把旧链接写进 permalink

如果链接规则变了,就做重定向

如果你不想逐篇使用 permalink,也可以在部署平台或服务器层做 301 重定向。

比如在 Vercel 项目里可以通过 vercel.json 配置:

json
{
  "redirects": [
    {
      "source": "/archives/:slug/",
      "destination": "/posts/:slug",
      "permanent": true
    }
  ]
}

不过重定向规则是否适用,取决于你旧站和新站的 URL 是否能一一对应。对于不规则的旧链接,还是逐篇 permalink 更稳。

第七步:迁移分类和标签

Typecho 的分类和标签可以直接映射到 blog-v3 的 Front matter。

Typecho:

txt
分类:技术
标签:Typecho、Nuxt、博客

blog-v3:

yaml
categories: [技术]
tags: [Typecho, Nuxt, 博客]

同时,建议在 blog.config.ts 中配置常用分类及图标:

ts
article: {
  categories: {
    未分类: { icon: 'tabler:circle-dashed' },
    技术: { icon: 'tabler:mouse', color: '#33aaff' },
    开发: { icon: 'tabler:code', color: '#7777ff' },
    杂谈: { icon: 'tabler:message', color: '#33bbaa' },
    生活: { icon: 'tabler:leaf', color: '#ff7777' },
  },
}

迁移时我更推荐先收敛分类,再迁移文章。因为很多老博客写久了之后,会出现分类过细、标签重复的问题,比如:

  • 前端Frontend前端开发
  • Linuxlinux
  • 随笔杂谈日记

迁移正好是一次整理信息架构的机会。

第八步:处理独立页面、友链和导航

Typecho 里除了文章,还可能有独立页面:

  • 关于;
  • 友链;
  • 留言板;
  • 归档;
  • 项目页。

迁移时不要只导文章,也要检查 typecho_contents 表里 type = 'page' 的内容。

sql
SELECT cid, title, slug, created, modified, text
FROM typecho_contents
WHERE type = 'page' AND status = 'publish';

在 blog-v3 里,有些页面可以继续使用 Markdown,有些页面更适合做成 Vue 页面。

比如友链,如果只是简单文字,可以写在 Markdown;如果想要卡片、分组、头像、描述,则更适合放在配置文件里,再由页面组件渲染。

我现在的 friend links 就是通过配置维护,页面负责展示。这样好处是:

  • 结构更清晰;
  • 头像、链接、描述、订阅地址可以统一管理;
  • 后续还可以扩展成博友圈、RSS 聚合等功能。

第九步:评论系统怎么处理

评论迁移通常有三种选择。

方案一:不迁移历史评论

这是最省事的方式。

个人博客很多文章的评论并不是核心内容,如果历史评论量不大,可以只保留数据库备份,不迁移到新站。

优点是简单,缺点是老文章下的讨论记录会丢失在前台。

方案二:导出成静态备份页面

可以把老评论导出成 JSON、Markdown 或 HTML,作为存档保留。

比如在每篇文章底部加一个“历史评论存档”折叠块,或者单独做一个评论归档页面。

方案三:迁移到新评论系统

如果新站使用 Twikoo、Waline、Artalk 等评论系统,可以尝试把 Typecho 评论表转换成对应系统的数据格式。

Typecho 评论一般在:

txt
typecho_comments

常用字段包括:

字段说明
coid评论 ID
cid文章 ID
created评论时间
author评论者
mail邮箱
url网址
text评论内容
parent父评论 ID
status评论状态

如果评论量很大,建议先写脚本导出,再根据新评论系统的导入格式转换。

我个人更倾向于:重要评论做静态备份,新评论交给新的评论系统处理。这样迁移成本低,也不会影响新站结构。

第十步:本地检查

文章转换完成后,不要马上部署,先本地跑一遍。

安装依赖:

bash
pnpm install

启动开发环境:

bash
pnpm dev

然后重点检查:

  • 首页文章列表是否正常;
  • 文章详情页是否能打开;
  • 分类、标签是否正常;
  • 图片是否显示;
  • 代码块高亮是否正常;
  • 内链是否正常;
  • RSS / Atom 是否正常;
  • sitemap 是否正常;
  • 移动端布局是否正常。

正式构建前再执行:

bash
pnpm build

如果构建失败,通常从以下几个方向排查:

  1. Front matter 写法错误;
  2. Markdown 中存在未闭合的代码块;
  3. YAML 数组格式不合法;
  4. 图片链接或组件语法写错;
  5. 某些 HTML 标签没有闭合。

其中最常见的是 Front matter 出错。比如标题里有冒号时,最好加引号:

yaml
title: "从 Typecho 到 blog-v3:迁移记录"

第十一步:部署到 Vercel

blog-v3 很适合部署到 Vercel。

基本流程是:

  1. 把项目推送到 GitHub;
  2. Vercel 导入仓库;
  3. 设置构建命令;
  4. 绑定域名;
  5. 后续提交自动部署。

常见构建配置:

txt
Install Command: pnpm install
Build Command: pnpm build
Output Directory: .output/public 或由 Nuxt/Vercel 自动识别

如果使用 Vercel CLI,也可以本地部署:

bash
pnpm dlx vercel deploy --prod

绑定域名后,把 DNS 指向 Vercel。生效后访问:

txt
https://你的域名/

再检查:

  • 首页;
  • 文章页;
  • 分类页;
  • 标签页;
  • 友链页;
  • Atom 订阅;
  • sitemap;
  • 旧链接是否能访问。

第十二步:迁移后的清理工作

上线不是结束,迁移后还有一些收尾工作。

检查死链

可以用爬虫或在线工具检查站内链接是否 404。

重点关注:

  • 老文章内链;
  • 图片链接;
  • 旧附件;
  • 友链;
  • 文章引用的外部链接。

提交搜索引擎

如果域名没变,搜索引擎会逐步重新抓取。但仍然建议提交 sitemap。

常见地址:

txt
https://你的域名/sitemap.xml

保留旧站备份

即使新站已经稳定运行,也建议至少保留一份旧站备份:

  • 数据库 SQL;
  • usr/uploads
  • config.inc.php
  • 主题和插件;
  • 转换脚本。

最好放到本地硬盘和云端各一份。

我踩过的一些坑

1. Front matter 不是随便写的

Markdown 顶部的 YAML 对格式非常敏感。

错误示例:

yaml
title: 从 Typecho 到 blog-v3: 迁移记录

这里标题里有英文冒号,可能导致 YAML 解析异常。建议写成:

yaml
title: "从 Typecho 到 blog-v3: 迁移记录"

2. 标签数组不要混用奇怪格式

建议统一使用:

yaml
tags: [Typecho, Nuxt, 博客]

或者:

yaml
tags:
  - Typecho
  - Nuxt
  - 博客

不要一会儿字符串,一会儿数组,否则后续统计和页面展示容易出问题。

3. 图片路径一定要提前规划

图片到底放在 public/uploads,还是继续使用 CDN,最好迁移前就定下来。否则文章导入后再改,会非常麻烦。

4. 旧链接最好不要全部放弃

如果博客已经运行很久,老链接可能散落在搜索引擎、社交平台、别人的文章里。能通过 permalink 保留就尽量保留,不能保留也尽量做 301 重定向。

5. 不要一次性相信自动转换结果

无论是 HTML 转 Markdown,还是批量替换图片,都要抽样检查。尤其是包含代码块、表格、数学公式、嵌入视频的文章,最容易出问题。

推荐迁移流程总结

如果重新来一次,我会按这个顺序做:

  1. 完整备份 Typecho 数据库和附件;
  2. 搭建 blog-v3 本地环境;
  3. 配置 blog.config.ts
  4. 从数据库导出文章、页面、分类、标签;
  5. 将正文转换为 Markdown;
  6. 迁移图片到 public/uploads 或 CDN;
  7. 使用 permalink 保留旧链接;
  8. 抽样检查文章渲染效果;
  9. 执行 pnpm build
  10. 部署到 Vercel;
  11. 绑定域名;
  12. 检查 sitemap、RSS、死链和搜索引擎收录。

结语

从 Typecho 迁移到 blog-v3,不只是换了一个博客程序,更像是换了一种写作和维护方式。

过去是“登录后台,写文章,点发布”;现在是“本地写 Markdown,提交 Git,自动部署”。

Typecho 的优雅在于简单直接,blog-v3 的舒服在于自由可控。文章、主题、配置、页面都在仓库里,修改有记录,部署可回滚,长期维护也更安心。

如果你的博客还很轻量,只想要一个稳定后台,Typecho 依然很好;但如果你更喜欢 Markdown、Git、现代前端工作流,并且希望博客能慢慢变成一个可以持续打磨的个人站点,那么 blog-v3 值得一试。

迁移过程会有点折腾,但当你看到所有文章都整齐地躺在 content/posts 里,并且每一次提交都能自动发布时,会觉得这一步很值得。

给 blog-v3 新建一个博友圈页面:聚合友链最新文章
组件样式示例

评论区

评论加载中...