Sanity 迁移指南:从 WordPress、Contentful 或 Drupal 迁移

在过去三年中,我将大约 40 个项目迁移到了 Sanity。有些是清爽的两周冲刺。其他的则变成了三个月的艰苦战役,让我质疑自己的职业选择。差异几乎从不取决于源 CMS — 而是取决于准备、内容建模决策以及对你实际要承担的事情的诚实态度。

这是我希望在开始进行 CMS 迁移时拥有的指南。它涵盖了从 WordPress、Contentful 和 Drupal 迁移到 Sanity GROQ 驱动世界的过程。我将直言不讳地讲述 Sanity 闪耀的地方、它会令你沮丧的地方,以及真实的时间表是什么样的。

目录

Sanity 迁移指南:从 WordPress、Contentful 或 Drupal 迁移

为什么团队在 2025 年迁移到 Sanity

让我们先把显而易见的事情说清楚。Sanity 的实时协作编辑、可定制的 Studio 和结构化内容方法确实很好。但是,大多数团队与我们联系进行迁移的原因并不是因为他们读了一篇关于 Sanity 功能的博客文章。而是因为某些东西坏掉了。

WordPress 网站在拥有 50,000+ 条内容和复杂自定义文章类型时会遇到扩展限制。Contentful 的定价模型在企业层级开始压制 — 我们看到团队面临着 $3,500+/月的账单,而这仅仅是一个内容 API。Drupal 团队找不到开发者了,至少找不到那些想在 2025 年使用 PHP 模板的开发者。

Sanity 的定价模型对大多数团队来说真的更可预测。免费层级涵盖每月最多 100K 个 API 请求和 500K 个资产。$99/月/项目的 Growth 计划可获得 2.5M 个 API 请求和 1M 个资产。相比之下,Contentful 的 Team 计划收费 $300/月,Contentful 的 Premium 层级很容易超过 $2,000/月。

但这是我诚实的看法:如果你当前的 CMS 工作正常,你的团队富有成效,就不要仅仅因为 Sanity 更新或更酷就迁移。迁移总是比你想象的要花费更多。

迁移前审计:每个人都跳过的步骤

在你编写一行迁移代码之前,你需要进行内容审计。不是快速扫描 — 而是实际的审计。它应该这样进行:

内容库存

记录每种内容类型、每个字段、每种关系。我使用带有这些列的电子表格:

  • 内容类型名称
  • 总项数
  • 字段(带类型)
  • 与其他内容类型的关系
  • 媒体附件(计数和总大小)
  • 自定义功能(短代码、小部件、嵌入)
  • 最后修改日期
  • 仍然相关吗?(是/否/可能)

你会震惊于有多少内容是死亡负担。在一次 WordPress 迁移中,客户有 12,000 篇文章。审计后,只有 4,200 篇仍然相关。那是 65% 少的内容要迁移、测试和验证。

技术依赖关系映射

列出你当前 CMS 使用的每个插件、模块或集成。对于每一个,确定:

  1. Sanity 可以本地处理这个吗?
  2. 是否有适用于它的 Sanity 插件?
  3. 我们需要构建自定义解决方案吗?
  4. 我们可以完全放弃这个吗?

仅仅这个映射就会为你节省数周的意外惊喜。

团队准备情况评估

Sanity Studio 基于 React。你的内容编辑器需要培训。你的开发人员需要学习 GROQ(或使用 GraphQL,尽管 GROQ 是 Sanity 真正闪耀的地方)。为团队入职预算 1-2 周 — 不是作为附加项,而是作为一个行项目。

WordPress 到 Sanity 的迁移

WordPress 是我们迁移最常见的源 CMS。这也是最棘手的,因为 WordPress 不仅仅是一个 CMS — 它是一个完整的应用程序平台,人们在其上添加了所有东西。

什么转移得很干净

  • 文章和页面(基本内容)
  • 分类和标签
  • 特色图像
  • 作者数据
  • 基本自定义字段(ACF、Meta Box)

什么变得混乱

  • Gutenberg 块:每种块类型都需要对应的 Sanity Portable Text 自定义块或对象类型。如果你有 15+ 个自定义 Gutenberg 块,请为此预留大量时间。
  • 短代码:这些需要被解析、解释并转换为 Portable Text 注释或自定义块。WPBakery 和 Elementor 短代码特别痛苦。
  • 插件生成的内容:WooCommerce 产品、Yoast SEO 数据、ACF 重复字段 — 每个都需要自定义迁移逻辑。
  • 媒体库:WordPress 存储多个图像大小。Sanity 即时处理图像转换,所以你只需要原始文件。但在混乱的 wp-uploads 文件夹中找到原始文件?很有趣的时光。

迁移脚本方法

我通常使用一个 Node.js 脚本,它会命中 WordPress REST API 并写入 Sanity 的 mutation API:

import { createClient } from '@sanity/client'
import fetch from 'node-fetch'

const sanity = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  token: process.env.SANITY_WRITE_TOKEN,
  apiVersion: '2025-01-01',
  useCdn: false,
})

const WP_API = 'https://yoursite.com/wp-json/wp/v2'

async function migratePosts(page = 1) {
  const res = await fetch(`${WP_API}/posts?per_page=100&page=${page}`)
  const posts = await res.json()
  const totalPages = res.headers.get('x-wp-totalpages')

  const transaction = sanity.transaction()

  for (const post of posts) {
    transaction.createOrReplace({
      _id: `wp-post-${post.id}`,
      _type: 'post',
      title: post.title.rendered,
      slug: { current: post.slug },
      publishedAt: post.date,
      // Body requires HTML-to-Portable-Text conversion
      body: await convertToPortableText(post.content.rendered),
    })
  }

  await transaction.commit()
  console.log(`Migrated page ${page} of ${totalPages}`)

  if (page < totalPages) {
    await migratePosts(page + 1)
  }
}

convertToPortableText 函数是 80% 复杂性所在的地方。我结合使用 @sanity/block-tools 包和 jsdom 进行 HTML 解析。它处理基本 HTML 很好,但自定义元素和短代码需要单个处理程序。

现实时间表

对于典型的 WordPress 网站,拥有 500-2,000 篇文章、标准自定义字段和少数自定义文章类型:4-8 周,包括内容建模、迁移脚本、验证和编辑器培训。

Sanity 迁移指南:从 WordPress、Contentful 或 Drupal 迁移 - 架构

Contentful 到 Sanity 的迁移

Contentful 到 Sanity 的迁移实际上是三个中最平顺的迁移路径。为什么?因为两者都是结构化内容平台,具有相似的心理模型。你的内容已经在一个无头 CMS 中,带有定义的内容类型和字段。

需要说明的关键差异

功能 Contentful Sanity
富文本 Rich Text(基于 JSON) Portable Text(基于 JSON)
内容建模 Web UI 代码定义的架构
查询语言 GraphQL / REST GROQ(+ GraphQL)
本地化 内置字段级 插件或自定义
引用 链接(Entry/Asset) 带类型的引用
Webhook
资产处理 内置 CDN Sanity CDN + hotspot/crop
定价(中级层) ~$300/月(Team) $99/月(Growth)

富文本转换

Contentful 的 Rich Text 和 Sanity 的 Portable Text 都是基于 JSON 的,这很好。但它们有不同的结构。你需要编写一个转换器:

function contentfulRichTextToPortableText(richTextField) {
  return richTextField.content.map(node => {
    switch (node.nodeType) {
      case 'paragraph':
        return {
          _type: 'block',
          style: 'normal',
          children: node.content.map(mapInlineContent),
        }
      case 'heading-2':
        return {
          _type: 'block',
          style: 'h2',
          children: node.content.map(mapInlineContent),
        }
      case 'embedded-entry-block':
        // Map to your custom Portable Text type
        return mapEmbeddedEntry(node)
      // ... handle all node types
    }
  }).filter(Boolean)
}

内容类型到架构映射

Contentful 内容类型映射相当直接到 Sanity 文档和对象类型。最大的转变是 Sanity 架构是在代码中定义的(JavaScript/TypeScript),而不是在 Web UI 中。这实际上是一个巨大的优势 — 你的内容模型位于版本控制中。

使用 Contentful Management API 导出你的内容模型,然后编写一个生成 Sanity 架构文件的脚本:

contentful space export --space-id YOUR_SPACE_ID --export-dir ./export

现实时间表

对于拥有 10-20 个内容类型和 5,000-10,000 个条目的 Contentful 空间:3-5 周。它更快,因为你已经在以结构化内容的方式思考。

Drupal 到 Sanity 的迁移

Drupal 迁移是那种让我倒第二杯咖啡的。不是因为 Drupal 不好 — 它是一个强大的系统。但 Drupal 网站往往很老,进行了大量自定义,并运行在没有人完全理解的基础设施上。

Drupal 特定的挑战

  • 50+ 个字段的内容类型:Drupal 使添加字段变得容易。太容易了。我见过拥有 80 个字段的内容类型,其中一半未使用。
  • 分类项引用:Drupal 的分类系统很灵活,但可以创建深层嵌套的需要为 Sanity 展平的层次结构。
  • Paragraphs 模块:如果网站使用 Drupal Paragraphs(大多数现代 Drupal 网站都使用),每种段落类型都变成一个 Portable Text 块类型或 Sanity 对象。这是最大的单一任务。
  • 媒体实体:Drupal 9/10 的媒体系统比 WordPress 的更复杂。多个媒体类型、可重用的媒体实体和文件字段配置都需要映射。
  • 多语言内容:Drupal 的翻译系统很复杂。Sanity 没有相同级别的内置本地化 — 你需要 @sanity/document-internationalization 插件或字段级方法。

迁移方法

我更喜欢使用 Drupal 的 JSON:API 模块(自 Drupal 9.x 起包含在核心中)作为提取层:

async function fetchDrupalContent(type, page = 0) {
  const limit = 50
  const offset = page * limit
  const url = `${DRUPAL_URL}/jsonapi/node/${type}?page[limit]=${limit}&page[offset]=${offset}&include=field_image,field_paragraphs`

  const res = await fetch(url, {
    headers: { Authorization: `Basic ${DRUPAL_AUTH}` },
  })
  return res.json()
}

对于没有 JSON:API 的旧 Drupal 7 网站,你可能需要直接查询数据库。Drupal 7 的数据库架构是……一种体验。field_data_* 表将萦绕你的梦想。

现实时间表

Drupal 迁移差异很大。一个直接的 Drupal 10 网站,拥有 5-10 个内容类型:5-8 周。一个遗留 Drupal 7 网站,拥有 30+ 个内容类型、Paragraphs 和多语言内容:8-16 周。我没有夸大。

内容建模:正确的架构

这是大多数迁移指南不会告诉你的:不要在 Sanity 中复制你的旧内容模型。这是你修复多年积累的内容债务的机会。

常见的建模错误

  1. 创建 1:1 字段映射:仅仅因为 WordPress 有一个"副标题"自定义字段并不意味着 Sanity 需要一个。也许它应该是一个结构化"hero"对象的一部分。
  2. 过度嵌套对象:Sanity 让你深度嵌套对象。抗拒这种冲动。平坦-ish 架构更容易用 GROQ 查询,编辑器也更容易使用。
  3. 忽视 Portable Text 的强大之处:不要只是将 HTML 转储到单个文本字段中。设计匹配你的内容模式的自定义块类型。一个"callout"块、一个"代码片段"块、一个"带标题的图像"块 — 这些让编辑的生活更美好。

架构设计流程

我遵循这个顺序:

  1. 审计现有内容(在迁移前完成)
  2. 确定实际的内容模式(不是 CMS 强加的)
  3. 先在纸上/白板上设计架构
  4. 在代码中构建架构
  5. 导入一个小测试批次(50-100 项)
  6. 让编辑测试 Studio 体验
  7. 在完整迁移之前迭代架构

步骤 5-7 至关重要,并且经常被跳过。我们已经在我们的无头 CMS 开发工作中更多地写了关于内容建模方法的文章。

数据迁移策略和工具

必要工具

  • @sanity/client:官方 JavaScript 客户端,用于读写 Sanity 数据
  • @sanity/block-tools:将 HTML 转换为 Portable Text
  • sanity dataset import/export:用于完整数据集操作的 CLI 工具
  • ndjson:Sanity 使用换行符分隔的 JSON 进行导入 — 熟悉它
  • jsdomhtmlparser2:用于在富文本转换期间进行 HTML 解析

迁移架构

我将每次迁移结构化为四个阶段的管道:

提取 → 转换 → 加载 → 验证

每个阶段都是一个单独的脚本。这很重要,因为你将运行迁移多次 — 通常在最终生产运行之前运行 5-10 次。有单独的阶段意味着你只能重新运行需要修复的部分。

# Extract
node scripts/extract-wordpress.js > data/raw-posts.ndjson

# Transform
node scripts/transform-posts.js < data/raw-posts.ndjson > data/sanity-posts.ndjson

# Load
sanity dataset import data/sanity-posts.ndjson production --replace

# Validate
node scripts/validate-migration.js

处理资产

图像和文件总是最慢的部分。Sanity 的资产管道很好,但上传 10,000 张图像需要时间。提示:

  • 首先上传资产,维护旧 URL 到新 Sanity 资产 ID 的映射
  • 使用并发上传(但尊重速率限制 — Growth 计划每秒 25 个请求)
  • 在上传前验证图像尺寸和格式
  • 不要迁移缩略图大小 — Sanity 通过其图像 CDN 即时生成这些

没有人谈论的隐性成本

让我老实告诉你那些不会出现在典型迁移估计中的成本。

URL 重定向

如果你更改你的前端(如果你迁移到无头 CMS,你可能会这样做),你需要重定向映射。每。一。个。URL。对于 SEO,这是不可转让的。拥有 5,000 页的网站需要 5,000 个重定向规则。工具如 next.config.js 重定向或 Vercel 的 _redirects 文件可以处理这个,但有人必须构建映射。

SEO 元数据迁移

来自 WordPress 的 Yoast SEO 数据。来自 Drupal 的 Metatag 模块数据。Contentful 的 SEO 字段。所有这些都需要转移。自定义元标题、描述、Open Graph 图像、规范 URL、结构化数据 — 它是项目中的一个项目。

编辑器培训和文档

预算最少 2-4 天。Sanity Studio 很直观,但与你的编辑所知不同。我们通常创建带有屏幕截图的自定义 Studio 文档,并录制 3-5 个演练视频。

前端开发

这是房间里的大象。迁移内容到 Sanity 只是项目的一半。你还需要一个使用内容的前端。无论你使用 Next.js、Astro 还是另一个框架,前端构建通常是总项目成本的 50-70%。如果你正在评估前端选项,请查看我们与 Next.jsAstro 的工作。

时间表和预算对比

基于我们在 2024-2025 年完成的项目:

迁移路径 内容量 复杂性 时间表 预算范围
WordPress → Sanity < 1,000 页 3-5 周 $8K-$15K
WordPress → Sanity 1,000-10,000 页 中等 6-10 周 $15K-$35K
WordPress → Sanity 10,000+ 页 10-16 周 $35K-$75K
Contentful → Sanity < 5,000 条目 低-中等 3-5 周 $7K-$18K
Contentful → Sanity 5,000-20,000 条目 中等 5-8 周 $18K-$40K
Drupal → Sanity < 2,000 节点 中等 5-8 周 $12K-$25K
Drupal → Sanity 2,000-15,000 节点 8-14 周 $25K-$60K
Drupal 7 → Sanity 任何 非常高 10-20 周 $35K-$90K

注:这些范围仅包括内容迁移。前端开发是额外的。有关特定项目的估计,请在 /pricing 与我们联系。

这些数字包括内容建模、迁移脚本、数据验证和基本编辑器培训。它们不包括前端开发、设计或持续维护。

迁移后检查清单

这是我在每次迁移时使用的检查清单:

  • 所有内容类型已迁移和验证
  • 所有引用/关系完整
  • 所有图像和文件已上传并正确链接
  • 富文本内容正确呈现(检查格式损坏)
  • URL 重定向就位并测试
  • SEO 元数据已迁移(标题、描述、OG 数据)
  • 生成了 XML 站点地图
  • 搜索控制台已使用新站点地图更新
  • 分析跟踪已保留
  • 已创建编辑器帐户并设置权限
  • 编辑器培训已完成
  • 内容预览(草稿模式)有效
  • 为构建触发器配置了 Webhook
  • 源 CMS 数据的备份已存档
  • 已规划 DNS 更改(如适用)
  • 性能基线已测量
  • 前 30 天的 404 监控已设置

最后一点很重要。无论你的重定向映射多么彻底,某些 URL 都会滑过去。在前一个月积极监控你的 404。

常见问题

典型的 WordPress 到 Sanity 迁移需要多长时间? 对于拥有 2,000 篇以下文章和直接自定义字段的标准 WordPress 网站,预期 4-8 周。这包括内容建模、迁移脚本、数据验证和编辑器培训。具有复杂 Gutenberg 块、WooCommerce 或多语言内容的网站可能需要 10-16 周。内容量的重要性不如你的内容类型的复杂性。

我可以从 Contentful 迁移到 Sanity 而不会丢失数据吗? 是的,这实际上是我这里涵盖的三个中最清晰的迁移路径。两个平台都使用结构化的、基于 JSON 的内容,所以映射是相对直接的。富文本需要从 Contentful 的格式转换为 Portable Text,引用需要重新映射,但你不应该丢失任何数据。我建议先对暂存数据集运行迁移,并在切换之前进行彻底的内容比较。

CMS 迁移期间我的 SEO 排名会发生什么? 这是让营销总监整晚睡不着觉的问题。如果你正确处理重定向、尽可能保持你的 URL 结构,并迁移所有 SEO 元数据,你应该看到最小的影响。Google 自己的文档说,正确重定向的迁移可能会看到 2-4 周的临时下滑,然后恢复。关键词是"正确"。跳过重定向映射,你会破坏你的排名。我们见过。

对于企业使用,Sanity 比 Contentful 便宜吗? 在大多数情况下,是的 — 有时非常便宜。Sanity 的 Growth 计划每月 $99 涵盖了你需要 Contentful 的 $300/月 Team 计划的内容。在企业规模,差异变得更大。Contentful 的 Premium 定价不是公开列出的,但通常每月运行 $2,000-$4,000+。Sanity Enterprise 定价也是自定义的,但对于等效使用通常成本更低。实际成本差异在于 API 调用 — Sanity 的包含限制更慷慨。

我应该将我的 Drupal 7 网站迁移到 Sanity 还是先升级到 Drupal 10? 直接去 Sanity。从 Drupal 7 迁移到 Drupal 10 的工作量几乎与迁移到不同 CMS 的工作量一样 — 版本之间的架构改变得那么戏剧性。如果你已经投资进行重大迁移,你不妨迁移到你实际上想长期使用的平台。一个例外:如果你的团队深入投入 Drupal 生态系统,只想现代化,那么 Drupal 10 加上无头前端是一个有效的路径。

迁移到 Sanity 时我需要重建我的前端吗? 如果你来自 WordPress 或 Drupal 等单体 CMS,是的 — 你需要一个新的前端,因为 Sanity 是无头的。这通常是项目的更大部分。如果你来自 Contentful,你通常可以重用你现有的前端,具有修改的 API 调用,尤其是如果你已经使用 Next.js 或类似框架。我们将 CMS 迁移和前端构建作为集成项目处理。

在迁移期间,我可以并行运行我的旧 CMS 和 Sanity 吗? 当然可以,我建议这样做。在初始迁移后,运行两个系统 2-4 周。编辑可以继续在旧 CMS 中工作,而你验证 Sanity 中的数据。只需在最终迁移运行之前冻结旧系统中的内容 — 你不想追赶一个移动的目标。一些团队在最后切换之前 48 小时前设置了"内容冻结"日期。

团队在 Sanity 迁移期间犯的最大错误是什么? 尝试在 Sanity 中精确复制他们的旧 CMS 结构。我经常看到这个。来自 WordPress 的团队尝试在 Sanity 中构建 WordPress 形状的架构 — 通用的"页面"类型,具有灵活的布局,而不是目的构建的内容类型。Sanity 的强度是结构化内容。使用迁移作为正确建模你的内容的机会。花一周额外时间进行内容建模。它将为你节省数月的痛苦。如果你想要指导,联系我们 — 内容建模是我们在迁移项目中花费最多时间的事情之一。