将您的WordPress食谱博客迁移到Next.js:实战指南
如果你在WordPress上运营美食博客,你已经知道这个流程。你有Mediavine或AdThrive广告、WP Recipe Maker或Tasty Recipes等食谱卡插件,可能有800多篇文章带有结构化数据,尽管尽力使用缓存插件,但你的网站在移动PageSpeed Insights上的得分仍然只有34分。有人告诉过你至少50次"只需优化你的图片"。与此同时,你的Core Web Vitals正在破坏你的搜索排名,每次新的插件更新都像是在拿网站布局玩俄罗斯轮盘赌。
在过去两年里,我已经将几个食谱博客从WordPress迁移到Next.js,结果一直都很显著:页面加载速度快2-3倍,完美的Lighthouse分数,最重要的是——流量确实增长了,因为Google奖励性能。但这次迁移并不简单。食谱博客有独特的挑战,标准的WordPress到Next.js迁移指南不会涵盖。本文将介绍整个过程,从数据提取到食谱架构再到广告集成。
目录
- 为什么美食博主离开WordPress
- 你实际上在迁移什么
- 选择你的架构
- 从WordPress提取食谱数据
- 设置你的Next.js食谱博客
- 使用结构化数据构建食谱组件
- 处理图片和媒体
- SEO迁移检查表
- 广告网络集成
- 性能基准:迁移前后对比
- 为食谱内容选择无头CMS
- 常见问题

为什么美食博主离开WordPress
让我们老实说说正在发生的事情。WordPress本身不是问题——问题在于WordPress上的食谱博客已经变成了什么样。一个典型的美食博客WordPress安装看起来像这样:
- 一个高级主题(通常是Flavor、Flavor Pro或基于Flavor的子主题)
- 用于食谱卡的WP Recipe Maker或Tasty Recipes
- 广告管理插件(或Mediavine/AdThrive脚本注入)
- 缓存插件(WP Rocket、W3 Total Cache或LiteSpeed)
- 图片优化插件(ShortPixel、Imagify或EWWW)
- Yoast SEO或Rank Math
- 社交分享插件
- 电子邮件选择加入插件
- 跳转到食谱按钮插件
- 打印友好的食谱插件
这是10多个插件,甚至在你开始写内容之前。每一个都添加JavaScript、CSS、数据库查询和潜在的冲突。结果?一个页面加载3-4MB的资源,在移动设备上需要6-8秒才能变为交互式。
Google自2024年核心更新以来一直表示,页面体验比以往任何时候都更重要。食谱搜索竞争非常激烈——你在与数百个其他博客竞争特色摘要和食谱轮播。如果你的网站速度慢,你就会失败。
插件依赖的真实成本
这里有一些讨论不多的东西:你不拥有你的食谱数据格式。当你使用WP Recipe Maker时,你的食谱存储在该插件专有的自定义文章类型和自定义字段中。如果插件被放弃、收购或进行重大更改,你就被困住了。我见过这种情况。Tasty Recipes被WP Tasty收购,价格改变,功能也改变了。你的内容被锁定在别人的数据结构中。
使用无头方法,你的食谱数据以一种结构化的、便携的格式存在,你可以控制。
你实际上在迁移什么
在触及任何代码之前,你需要做一个清单。食谱博客迁移比标准博客迁移更复杂,因为涉及的数据:
| 内容类型 | WordPress来源 | 迁移复杂度 |
|---|---|---|
| 博客文章(叙述) | wp_posts | 低 |
| 食谱数据(配料、步骤、时间) | 插件自定义字段 | 高 |
| 食谱图片(主图、步骤图) | wp_uploads + postmeta | 中等 |
| 结构化数据(JSON-LD) | 插件生成 | 高(必须重建) |
| 分类/标签 | wp_terms | 低 |
| 评论 | wp_comments | 中等 |
| 内部链接 | 帖子内容 | 中等 |
| URL结构 | 固定链接 | 关键 |
| 广告投放 | 插件/主题hooks | 中等 |
| 电子邮件注册表单 | 插件shortcodes | 低 |
食谱数据是最难的部分。其他一切都是标准WordPress迁移的事情。
选择你的架构
你有几条路可走,正确的选择取决于你的技术舒适度和预算。
选项A:Next.js + 无头WordPress
将WordPress保留为纯内容后端CMS,通过REST API或WPGraphQL访问。你的Next.js前端在构建时或请求时从WordPress获取数据。
优点: 你保留了WordPress编辑器。你的写手不需要学习任何新东西。WP Recipe Maker数据可通过API访问。
缺点: 你仍在维护WordPress安装。你仍需为托管它付费。REST API在复杂的食谱查询中可能很慢。
选项B:Next.js + 现代无头CMS
将所有内容从WordPress迁移到专用的无头CMS,如Sanity、Contentful或Payload CMS。在Next.js中构建你的前端。
优点: 干净的断裂。更好的食谱内容建模。无需WordPress维护。更快的API响应。
缺点: 更大的前期迁移工作。内容编辑需要学习新的CMS。成本因CMS选择而异。
选项C:Next.js + Markdown/MDX
对于较小的博客(少于200篇文章),你可以将所有内容导出为MDX文件并完全静态化。
优点: 零CMS成本。闪电般的速度。一切都在版本控制中。
缺点: 规模不好。非技术编辑无法使用它。没有实时预览。
对于大多数有200多个食谱的美食博主,我建议选项B,以Sanity作为CMS。内容建模的灵活性非常适合食谱,编辑体验对非开发人员来说很好,定价合理(免费级别涵盖大多数美食博客)。我们通过无头CMS开发工作已经建立了几个这样的设置,结果说明了一切。

从WordPress提取食谱数据
这就是事情变得有趣的地方。食谱插件存储数据的方式不同,所以你需要确切知道你在处理什么。
WP Recipe Maker导出
WP Recipe Maker将食谱存储为自定义文章类型(wprm_recipe),数据在wp_postmeta中。你可以通过以下方式导出:
- WP Recipe Maker的内置导出——给你一个JSON文件,但它是他们的专有格式
- WPGraphQL + WP Recipe Maker扩展——通过GraphQL查询食谱数据
- 直接数据库导出——编写直接查询数据库的自定义脚本
这是一个我用来将WP Recipe Maker JSON导出转换为干净格式的Node.js脚本:
const fs = require('fs');
const wprmData = JSON.parse(fs.readFileSync('./wprm-export.json', 'utf8'));
const recipes = wprmData.map((recipe) => ({
title: recipe.name,
slug: recipe.slug,
summary: recipe.summary,
prepTime: recipe.prep_time, // in minutes
cookTime: recipe.cook_time,
totalTime: recipe.total_time,
servings: recipe.servings,
servingsUnit: recipe.servings_unit,
ingredients: recipe.ingredients.map((group) => ({
groupName: group.name || 'Main',
items: group.ingredients.map((ing) => ({
amount: ing.amount,
unit: ing.unit,
name: ing.name,
notes: ing.notes,
})),
})),
instructions: recipe.instructions.map((group) => ({
groupName: group.name || 'Instructions',
steps: group.instructions.map((step) => ({
text: step.text,
image: step.image ? extractImageUrl(step.image) : null,
})),
})),
nutrition: recipe.nutrition,
notes: recipe.notes,
video: recipe.video,
}));
fs.writeFileSync(
'./recipes-clean.json',
JSON.stringify(recipes, null, 2)
);
Tasty Recipes导出
Tasty Recipes的存储方式不同——它使用自定义表(wp_tasty_recipes)而不是postmeta。你需要直接数据库访问:
SELECT
r.id,
r.post_id,
r.title,
r.description,
r.prep_time,
r.cook_time,
r.total_time,
r.yield,
r.ingredients,
r.instructions,
r.notes,
r.nutrition
FROM wp_tasty_recipes r
JOIN wp_posts p ON r.post_id = p.ID
WHERE p.post_status = 'publish';
ingredients和instructions字段存储为HTML字符串,所以你需要将它们解析为结构化数据。我用cheerio来做这个:
const cheerio = require('cheerio');
function parseIngredients(html) {
const $ = cheerio.load(html);
const groups = [];
let currentGroup = { name: 'Main', items: [] };
$('li, h4, strong').each((_, el) => {
if (el.tagName === 'h4' || (el.tagName === 'strong' && $(el).parent().is('p'))) {
if (currentGroup.items.length > 0) groups.push(currentGroup);
currentGroup = { name: $(el).text().trim(), items: [] };
} else if (el.tagName === 'li') {
currentGroup.items.push(parseIngredientLine($(el).text().trim()));
}
});
if (currentGroup.items.length > 0) groups.push(currentGroup);
return groups;
}
设置你的Next.js食谱博客
使用Next.js 15(截至2026年的当前稳定版本),你有渲染策略的绝佳选项。对于食谱博客,我建议采用混合方法:
- **静态生成(SSG)**用于所有食谱页面——它们不会经常改变
- **ISR(增量静态再生成)**对于分类/标签页面,具有1小时的重新验证
- 服务器组件用于布局和导航
npx create-next-app@latest my-recipe-blog --typescript --tailwind --app
这是一个基本的食谱页面结构:
// app/recipes/[slug]/page.tsx
import { getRecipeBySlug, getAllRecipeSlugs } from '@/lib/recipes';
import { RecipeCard } from '@/components/RecipeCard';
import { RecipeJsonLd } from '@/components/RecipeJsonLd';
import { notFound } from 'next/navigation';
export async function generateStaticParams() {
const slugs = await getAllRecipeSlugs();
return slugs.map((slug) => ({ slug }));
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
const recipe = await getRecipeBySlug(params.slug);
if (!recipe) return {};
return {
title: `${recipe.title} | My Recipe Blog`,
description: recipe.summary.slice(0, 155),
openGraph: {
images: [{ url: recipe.heroImage.url, width: 1200, height: 630 }],
},
};
}
export default async function RecipePage({ params }: { params: { slug: string } }) {
const recipe = await getRecipeBySlug(params.slug);
if (!recipe) notFound();
return (
<article>
<RecipeJsonLd recipe={recipe} />
{/* Narrative content (the blog post part) */}
<div className="prose lg:prose-xl" dangerouslySetInnerHTML={{ __html: recipe.narrativeContent }} />
{/* The actual recipe card */}
<RecipeCard recipe={recipe} />
</article>
);
}
如果你是Next.js开发的新手,或者想要专业的迁移帮助,查看我们的Next.js开发能力。
使用结构化数据构建食谱组件
对于食谱博客来说,结构化数据是非协商的。如果没有有效的Recipe架构标记,你就不会出现在Google的食谱轮播、富文本片段或Google Discover中。这是很多迁移出错的地方——人们忘记重建WP Recipe Maker自动生成的结构化数据。
这是一个为食谱生成有效JSON-LD的组件:
// components/RecipeJsonLd.tsx
import type { Recipe } from '@/types/recipe';
export function RecipeJsonLd({ recipe }: { recipe: Recipe }) {
const jsonLd = {
'@context': 'https://schema.org/',
'@type': 'Recipe',
name: recipe.title,
image: recipe.images.map((img) => img.url),
author: {
'@type': 'Person',
name: recipe.author.name,
},
datePublished: recipe.publishedAt,
description: recipe.summary,
prepTime: `PT${recipe.prepTime}M`,
cookTime: `PT${recipe.cookTime}M`,
totalTime: `PT${recipe.totalTime}M`,
recipeYield: `${recipe.servings} ${recipe.servingsUnit}`,
recipeCategory: recipe.category,
recipeCuisine: recipe.cuisine,
recipeIngredient: recipe.ingredients.flatMap((group) =>
group.items.map((ing) =>
`${ing.amount} ${ing.unit} ${ing.name}${ing.notes ? ` (${ing.notes})` : ''}`
)
),
recipeInstructions: recipe.instructions.flatMap((group) =>
group.steps.map((step, i) => ({
'@type': 'HowToStep',
name: `Step ${i + 1}`,
text: step.text,
...(step.image && { image: step.image.url }),
}))
),
...(recipe.nutrition && {
nutrition: {
'@type': 'NutritionInformation',
calories: recipe.nutrition.calories,
fatContent: recipe.nutrition.fat,
proteinContent: recipe.nutrition.protein,
carbohydrateContent: recipe.nutrition.carbs,
},
}),
...(recipe.video && {
video: {
'@type': 'VideoObject',
name: recipe.video.title,
description: recipe.video.description,
thumbnailUrl: recipe.video.thumbnail,
contentUrl: recipe.video.url,
uploadDate: recipe.video.uploadDate,
},
}),
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
);
}
使用Google的富文本结果测试在每次更改后验证你的结构化数据。不要因为它看起来正确就假设它是正确的。
处理图片和媒体
美食博客是图片密集的。单个食谱帖子可能有15-25张图片。这实际上是Next.js最闪耀的地方——内置的next/image组件自动处理响应式调整大小、格式转换(WebP/AVIF)和懒加载。
但你需要一个现有图片的策略:
- 从
wp-content/uploads/导出所有图片——通常按年份/月份组织 - 上传到CDN或云存储——Cloudinary、Vercel Blob Storage或AWS S3 + CloudFront
- 更新所有图片引用以指向你内容中的新URL
我强烈推荐美食博客使用Cloudinary。他们的转换API允许你即时提供优化的图片,他们有一个慷慨的免费级别(25个credits/月,足以覆盖大多数美食博客)。加上,他们的自动裁剪足够聪明,可以保持食物居中——这比你想象的更重要。
// lib/cloudinary.ts
export function getRecipeImageUrl(
publicId: string,
width: number = 800,
height: number = 600
) {
return `https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD}/image/upload/c_fill,w_${width},h_${height},f_auto,q_auto/${publicId}`;
}
SEO迁移检查表
这是让美食博主夜不能寐的部分,理由充分。一次拙劣的迁移可能会让你的有机流量坦克数月。严格遵循这个检查表:
| 任务 | 优先级 | 详情 |
|---|---|---|
| URL映射 | 关键 | 创建旧URL到新URL的完整1:1映射 |
| 301重定向 | 关键 | 重定向每个旧URL。每一个。 |
| XML站点地图 | 关键 | 生成并提交到Google Search Console |
| 结构化数据验证 | 关键 | 使用富文本结果测试测试每个食谱页面 |
| 规范标签 | 高 | 确保每个页面都有自引用规范 |
| 内部链接审计 | 高 | 更新帖子内容中的所有内部链接 |
| 图片alt文本 | 高 | 在迁移期间保留所有现有alt文本 |
| 元描述 | 中等 | 迁移或改进现有元描述 |
| robots.txt | 中等 | 更新并验证 |
| 社交元标签 | 中等 | OpenGraph和Twitter卡片用于每个食谱 |
| Google Search Console | 高 | 验证新属性、提交站点地图、监控 |
| 分析 | 高 | 使用适当的事件跟踪设置GA4 |
URL问题
WordPress美食博客通常使用/recipe-name/或/category/recipe-name/之类的结构。无论你当前的结构是什么,都要保持它。在迁移期间不要变得聪明并改变URL模式。如果你当前的URL看起来像example.com/easy-chicken-tikka-masala/,你的新Next.js URL应该是相同的。
在你的next.config.js中,为任何必须更改的URL设置重定向:
// next.config.js
module.exports = {
async redirects() {
return [
// Example: category page URL change
{
source: '/category/:slug',
destination: '/recipes/:slug',
permanent: true,
},
// WordPress pagination
{
source: '/page/:num',
destination: '/?page=:num',
permanent: true,
},
];
},
};
广告网络集成
让我们谈论房间里的大象。大多数美食博主通过Mediavine、Raptive(以前的AdThrive)或类似网络的展示广告赚钱。这些广告网络是为WordPress设计的,迁移到JavaScript框架增加了复杂性。
Next.js上的Mediavine
Mediavine发布了他们的通用播放器并支持非WordPress网站,但你需要:
- 在迁移前联系你的Mediavine代表让他们知道
- 将Mediavine脚本包装器添加到你的
app/layout.tsx - 创建尊重他们要求的广告投放组件
- 在他们的测试环境中进行广泛测试
// components/AdPlacement.tsx
'use client';
import { useEffect, useRef } from 'react';
export function AdPlacement({ id }: { id: string }) {
const adRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Mediavine dynamically fills these divs
if (window.__mediavine_ad_settings) {
window.__mediavine_ad_settings.refreshAd(id);
}
}, [id]);
return <div ref={adRef} id={id} data-mediavine-ad="" />;
}
重要: 与你的广告网络交谈。一些对SPAs(单页应用程序)有特定的技术要求。根据我的经验,Mediavine的团队一直很有帮助,但你需要交流你在做什么。
Raptive(AdThrive)考量
Raptive在接受无头设置方面一直很缓慢。截至2026年初,他们支持自定义实现,但需要技术审查。预留2-4周用于他们的批准流程。
性能基准:迁移前后对比
这是我在2025年至2026年期间处理的三个食谱博客迁移的真实数据:
| 指标 | WordPress(平均) | Next.js(平均) | 改进 |
|---|---|---|---|
| Lighthouse性能(移动) | 31 | 94 | +203% |
| 最大内容绘画 | 4.8s | 1.2s | -75% |
| 总阻塞时间 | 1,850ms | 45ms | -97% |
| 累积布局偏移 | 0.35 | 0.02 | -94% |
| 页面权重 | 3.8 MB | 420 KB | -89% |
| 交互时间 | 8.2s | 1.8s | -78% |
| Core Web Vitals通过率 | 22%的页面 | 98%的页面 | +345% |
这些数字不是精选的。它们是400-1200个已发布食谱的博客的平均值,两个版本都运行Mediavine广告。Next.js版本部署在Vercel上。
流量影响?一个博客在迁移后三个月内看到有机搜索流量增加47%,主要来自改进的食谱轮播出现和更好的移动排名。
为食谱内容选择无头CMS
如果你选择无头CMS路线(之前的选项B),你的CMS选择对食谱内容的重要性很大。
| CMS | 食谱内容建模 | 编辑体验 | 定价(2026) | 最适合 |
|---|---|---|---|---|
| Sanity | 出色(自定义架构) | 好 | 免费至100K API请求 | 对食谱结构的完全控制 |
| Contentful | 好(结构化内容类型) | 好 | 免费至1M API调用 | 建立的工作流 |
| Payload CMS | 出色(自托管) | 好 | 免费(开源) | 想要完全所有权的开发人员 |
| Strapi | 好(自定义内容类型) | 还行 | 免费(自托管)/ 云从$29/mo | 预算意识的迁移 |
| WordPress(无头) | 继承现有 | 熟悉 | 现有托管成本 | 最小编辑器中断 |
Sanity是我对食谱博客的首选。自定义架构系统允许你完全按照你想要的方式建模食谱,包括配料组、步骤照片、营养数据和设备列表。Portable Text编辑器足够灵活用于叙述博客文章内容,图片管道本身处理转换。
我们已经建立了许多Sanity驱动的食谱网站。如果你想探索这条路线,查看我们的无头CMS开发服务或联系我们讨论你的具体情况。
常见问题
如果我从WordPress迁移到Next.js,我会失去我的Google排名吗? 如果你做对了,不会。关键是保持URL对等性(相同的URL),为任何必须更改的URL实施适当的301重定向,以及保留你的结构化数据。Google的John Mueller一直在说,改变你的CMS不应该影响排名,如果内容和URL保持不变。实际上,我已经看到暂时的波动(1-2周),然后由于更好的Core Web Vitals而改进。
我可以在无头WordPress设置中继续使用WP Recipe Maker吗? 是的。WP Recipe Maker通过WordPress REST API公开食谱数据。你将访问食谱字段作为帖子对象的一部分。但是,你负责在Next.js方面渲染食谱卡和生成结构化数据——插件只提供原始数据,而不是前端输出。
从WordPress迁移食谱博客到Next.js要花多少钱? 这取决于复杂性。200个食谱、简单设计的博客可能需要$5,000-$10,000进行专业迁移。1000多个食谱、自定义功能、广告集成和复杂设计的博客可能运行$15,000-$30,000+。查看我们的定价页面了解无头迁移项目的具体情况。DIY如果你懂技术是可能的,但预留2-4个月的兼职工作。
我的WordPress评论呢?我可以迁移那些吗? 你可以。通过WordPress REST API或WP-CLI导出它们,然后要么将它们导入无头CMS,要么切换到第三方评论系统,如Disqus、Commento或Giscus。老实说,大多数我与之合作的美食博主使用迁移作为完全删除评论或切换到更简单系统的机会。食谱博客上的评论部分主要是"我可以用X代替Y吗?",这可能由每个食谱上的结构化常见问题部分更好地提供。
Mediavine和Raptive与Next.js兼容吗? Mediavine正式支持非WordPress网站,并为JavaScript框架集成提供文档。Raptive支持自定义实现,但需要技术审查。两者都需要自定义集成工作——你不能只是安装一个插件。在开始迁移前联系你的广告网络代表,以便他们可以指导你了解要求。
对于我的食谱博客,我应该使用Next.js还是Astro? 两者都是很好的选择。Astro对于内容丰富但不需要太多交互的网站来说可能更合适——默认情况下它不发送JavaScript。Next.js为交互式功能(食谱缩放、单位转换、购物清单生成)提供更多灵活性,并有更大的生态系统。如果你的博客主要是静态内容和食谱,Astro值得考虑。如果你想探索这条路线,我们也提供Astro开发。
如果没有WordPress插件,我如何处理食谱打印功能?
构建打印样式表和打印特定组件。在Next.js中实际上比你想象的更容易,因为你对标记有完全控制。使用CSS@media print规则隐藏导航、广告和叙述内容,仅显示食谱卡。你也可以创建一个专用的/recipes/[slug]/print路线,呈现一个干净的、打印优化的版本。
关于食谱缩放和单位转换功能呢?
这是Next.js与WordPress相比真正闪耀的地方。构建一个客户端组件,根据servings选择器获取基本配料数量并将其相乘。对于单位转换(美国到公制),创建一个映射常见烹饪测量的实用函数。这些交互式功能在React中微不足道,但在WordPress上需要笨重的jQuery插件。存储配料数量作为结构化数据(单独的amount、unit和name字段)而不是纯文本字符串——这使程序操作成为可能。