总结

从WordPress迁移到Next.js(2026年)会带来可衡量的性能提升:平均TTFB从1,200ms下降到85ms,页面权重从3.2 MB缩小到620 KB,Lighthouse评分从42跃升到94。该过程涉及通过WP REST API将内容导出到Supabase或Payload CMS、将媒体迁移到对象存储、为所有URL映射301重定向、在Next.js Metadata API中保留Yoast/RankMath SEO数据,以及用Server Actions替换Gravity Forms等插件。对于WooCommerce网站,Stripe替代了整个商务栈。预计典型网站(100-500页)需要4-8周,最大的时间投入在于重定向测试和模板重建。

这不是一个夸大其词的观点。我从事WordPress开发已有12年多。我已发布过代理商网站、会员平台、月收入六位数的WooCommerce商店以及无数自定义文章类型的网站。我也已将生产网站迁移到Next.js + Supabase。以下是每个技术细节——哪些能平稳映射,哪些不能,以及需要什么规划。

我不会假装WordPress很差。它不差。它为43%的网络提供支持是有充分理由的。但对于某些项目——性能是业务指标的网站、安全表面积很重要的网站、你想拥有部署管道的网站——Next.js是更好的工具。但是迁移?如果计划不周,这是个地雷阵。

本指南涵盖我使用的确切流程,包含真实代码、真实陷阱以及对你将获得和失去的内容的诚实评估。

目录

WordPress到Next.js迁移:完整技术指南

内容迁移:WP REST API到Supabase或Payload CMS

每个WordPress迁移都从这里开始。你有文章、页面、自定义文章类型、ACF字段、分类法——多年的内容需要安全地存放在某处。

该内容的去向有两个不错的选择:

  • Supabase ——如果你想要一个完全可控的数据库,具有行级安全和开箱即用的REST/GraphQL API
  • Payload CMS ——如果你的客户需要一个在WordPress之后感觉熟悉的可视化编辑体验

对于我们的无头CMS开发项目,我们根据具体客户进行评估。编辑需要自助服务时选择Payload。开发人员是主要内容管理者或需要数据用于网站以外的用途时选择Supabase。

从WordPress迁移到Next.js时应该保留哪些内容结构?

迁移期间保留文章元数据、分类法、自定义字段和URL slug。你的WordPress文章包含多年的结构化数据:分类、标签、ACF字段、特色图片和发布日期。通过WP REST API使用_embed参数导出所有这些,在单个请求中获取媒体URL。存储内容的HTML和Markdown两个版本——HTML作为后备,Markdown用于MDX渲染。将自定义文章类型映射到新系统中的等效数据库表或CMS集合。

导出脚本

以下是我用来从WP REST API拉取内容、清理并将其插入到Supabase的Node.js脚本。这处理文章,但你会为页面和CPT复制该模式(只需更改端点)。

import { createClient } from '@supabase/supabase-js';
import TurndownService from 'turndown';

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_SERVICE_KEY
);

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

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

async function fetchAllPosts() {
  let page = 1;
  let allPosts = [];
  let hasMore = true;

  while (hasMore) {
    const res = await fetch(
      `${WP_API}/posts?per_page=100&page=${page}&_embed`
    );

    if (!res.ok) break;

    const posts = await res.json();
    allPosts = allPosts.concat(posts);

    const totalPages = parseInt(res.headers.get('X-WP-TotalPages'));
    hasMore = page < totalPages;
    page++;
  }

  return allPosts;
}

async function migrateContent() {
  const posts = await fetchAllPosts();
  console.log(`Fetched ${posts.length} posts from WordPress`);

  const transformed = posts.map((post) => ({
    wp_id: post.id,
    title: post.title.rendered,
    slug: post.slug,
    content_html: post.content.rendered,
    content_markdown: turndown.turndown(post.content.rendered),
    excerpt: post.excerpt.rendered.replace(/<[^>]*>/g, '').trim(),
    published_at: post.date,
    status: post.status,
    featured_image:
      post._embedded?.['wp:featuredmedia']?.[0]?.source_url || null,
    categories:
      post._embedded?.['wp:term']?.[0]?.map((t) => t.name) || [],
    tags:
      post._embedded?.['wp:term']?.[1]?.map((t) => t.name) || [],
  }));

  const { data, error } = await supabase
    .from('posts')
    .upsert(transformed, { onConflict: 'wp_id' });

  if (error) {
    console.error('Migration failed:', error);
  } else {
    console.log(`Migrated ${transformed.length} posts to Supabase`);
  }
}

migrateContent();

我艰难学到的几件事:

  • 始终在WP REST API调用中使用_embed。没有它,你获得的是媒体ID而不是URL,这意味着需要N+1个请求来解析特色图片。
  • Turndown将HTML转换为Markdown ——如果你计划稍后使用MDX进行渲染,这至关重要。也保留原始HTML作为后备。
  • 短代码不会保存。 WordPress通过REST API渲染某些短代码,但许多(特别是来自WPBakery或Elementor等插件的)会以原始括号文本形式出现。你需要一个短代码到组件的映射策略。我保留一个电子表格。
  • ACF/自定义字段:如果使用ACF,你需要启用ACF to REST API插件,然后自定义字段会以每个文章对象的acf属性形式出现。

Supabase表架构

CREATE TABLE posts (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  wp_id INTEGER UNIQUE,
  title TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  content_html TEXT,
  content_markdown TEXT,
  excerpt TEXT,
  published_at TIMESTAMPTZ,
  status TEXT DEFAULT 'publish',
  featured_image TEXT,
  categories TEXT[],
  tags TEXT[],
  seo_title TEXT,
  seo_description TEXT,
  og_image TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

注意我包含了seo_titleseo_descriptionog_image列。你在下方SEO迁移部分需要这些。

媒体迁移:从wp-content到Supabase Storage

这是大多数指南都掠过的部分,也是耗时最长的部分。一个12年老的WordPress网站轻易可能在wp-content/uploads/中有10,000+个文件。

该方法:

  1. 下载整个wp-content/uploads/目录
  2. 上传到Supabase Storage(或Cloudflare R2或S3)
  3. 重写内容中的每个媒体URL

如何将WordPress媒体文件迁移到现代对象存储?

下载整个wp-content/uploads/目录,上传到Supabase Storage或Cloudflare R2,然后重写内容中的所有媒体URL。使用脚本从你的WordPress内容中获取每个图像URL,上传到对象存储并保留目录结构(2024/03/image.jpg),然后运行第二轮以用新存储URL替换旧URL。为直接链接到你旧图像URL的外部网站设置通配符重定向。

下载和上传脚本

import { createClient } from '@supabase/supabase-js';
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_SERVICE_KEY
);

const BUCKET = 'media';

async function migrateMedia(posts) {
  const urlRegex =
    /https?:\/\/yoursite\.com\/wp-content\/uploads\/[^\s"')]+/g;

  for (const post of posts) {
    const urls = post.content_html.match(urlRegex) || [];

    for (const url of urls) {
      try {
        const res = await fetch(url);
        const buffer = Buffer.from(await res.arrayBuffer());

        // Preserve directory structure: 2024/03/image.jpg
        const storagePath = url.replace(
          /https?:\/\/yoursite\.com\/wp-content\/uploads\//,
          ''
        );

        const { error } = await supabase.storage
          .from(BUCKET)
          .upload(storagePath, buffer, {
            contentType: res.headers.get('content-type'),
            upsert: true,
          });

        if (error) console.error(`Failed: ${storagePath}`, error);
        else console.log(`Uploaded: ${storagePath}`);
      } catch (e) {
        console.error(`Skipped: ${url}`, e.message);
      }
    }
  }
}

async function rewriteUrls() {
  const { data: posts } = await supabase.from('posts').select('*');
  const supabaseBase = `${process.env.SUPABASE_URL}/storage/v1/object/public/${BUCKET}`;

  for (const post of posts) {
    const updated = post.content_html.replace(
      /https?:\/\/yoursite\.com\/wp-content\/uploads\//g,
      `${supabaseBase}/`
    );

    await supabase
      .from('posts')
      .update({
        content_html: updated,
        content_markdown: turndown.turndown(updated),
      })
      .eq('id', post.id);
  }
}

图像性能收益

这是迁移真正带来回报的地方。WordPress提供原始上传——通常是人们从数码单反相机上传的3000×2000px PNG。即使有ShortPixel这样的插件,你仍然通过PHP提供图像。

Next.js<Image>组件与next/image进行自动格式协商(WebP/AVIF)、响应式尺寸调整和懒加载。我们上次迁移的数字:

指标 WordPress Next.js + Image组件
平均页面图像权重 2.1 MB 380 KB
图像请求 每页12个 6个(懒加载)
格式 JPEG/PNG WebP(支持AVIF)
累积布局偏移 0.18 0.02

那不是打字错误。平均图像载荷从2.1 MB下降到380 KB。 这是在没有重新上传优化文件的情况下——只是让next/image完成工作。

import Image from 'next/image';

export function PostImage({ src, alt }: { src: string; alt: string }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={800}
      height={450}
      sizes="(max-width: 768px) 100vw, 800px"
      quality={80}
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,..." // 在构建时生成
    />
  );
}

URL结构:映射每个旧URL

这是迁移失败的地方。一个遗漏的重定向意味着一个页面的404,而Google已为其编制索引多年。我以与数据库迁移相同的认真度处理URL映射——测试它、验证它,然后再次验证。

在WordPress迁移期间处理URL重定向的正确方法是什么?

从WordPress导出每个已发布的URL,与Google Search Console索引的URL进行交叉引用,然后对所有URL实施301重定向。查询你的wp_posts表以获取所有已发布的URL,导出GSC的索引URL,然后创建一个重定向映射。对于少于50个URL使用next.config.js重定向,对于50-1,024个URL使用JSON文件,或对于超过Vercel 1,024重定向限制的网站使用中间件。包括针对分类页面、分页和wp-content/uploads路径的通配符重定向。

映射过程

首先,从WordPress导出每个URL。我直接从数据库拉取:

SELECT
  CONCAT('/', post_name, '/') AS old_url,
  post_type,
  post_status
FROM wp_posts
WHERE post_status = 'publish'
  AND post_type IN ('post', 'page', 'product')
ORDER BY post_type, post_name;

然后与Google Search Console的索引URL进行交叉引用。GSC经常显示你的数据库中不存在的URL——旧分类页面、分页URL、附件页面。你需要对所有这些进行重定向。

next.config.js重定向

对于少于50个重定向的网站,内联它们:

// next.config.js
module.exports = {
  async redirects() {
    return [
      {
        source: '/2019/03/old-post-slug/',
        destination: '/blog/old-post-slug',
        permanent: true,
      },
      {
        source: '/category/:slug',
        destination: '/blog/category/:slug',
        permanent: true,
      },
      {
        source: '/product/:slug',
        destination: '/shop/:slug',
        permanent: true,
      },
    ];
  },
};

对于200+个重定向:使用JSON文件

一旦超过几百个,维护内联重定向会很痛苦。我使用JSON文件:

// redirects.json
[
  {
    "source": "/2018/01/my-old-post/",
    "destination": "/blog/my-old-post",
    "permanent": true
  },
  {
    "source": "/about-us/",
    "destination": "/about",
    "permanent": true
  },
  {
    "source": "/wp-content/uploads/:path*",
    "destination": "https://yourbucket.supabase.co/storage/v1/object/public/media/:path*",
    "permanent": true
  }
]
// next.config.js
const redirectsList = require('./redirects.json');

module.exports = {
  async redirects() {
    return redirectsList;
  },
};

wp-content/uploads的通配符重定向至关重要。会有外部网站直接链接到你的图像。不要失去这些反向链接。

重要:Vercel在next.config.js中有1,024个重定向的限制。对于超过该限制的网站,使用中间件:

// middleware.ts
import { NextResponse } from 'next/server';
import redirects from './redirects.json';

const redirectMap = new Map(
  redirects.map((r) => [r.source, r])
);

export function middleware(request) {
  const redirect = redirectMap.get(request.nextUrl.pathname);
  if (redirect) {
    return NextResponse.redirect(
      new URL(redirect.destination, request.url),
      redirect.permanent ? 308 : 307
    );
  }
}

WordPress到Next.js迁移:完整技术指南——架构

SEO迁移:Yoast和RankMath数据到Next.js Metadata

如果你一直在使用Yoast或RankMath,你有多年的自定义元标题、描述和Open Graph数据存储在wp_postmeta表中。不要丢失它。

迁移到Next.js时如何保留Yoast SEO数据?

从wp_postmeta导出Yoast元标题、描述和Open Graph图像,将它们存储在新数据库中,然后使用Next.js Metadata API渲染它们。查询wp_postmeta以获取_yoast_wpseo_title、_yoast_wpseo_metadesc和_yoast_wpseo_opengraph-image字段。将此数据导入到你的文章表中的专用SEO列。使用Next.js App Router中的generateMetadata将此数据呈现为正确的元标签和Open Graph标记。

导出SEO数据

SELECT
  p.post_name AS slug,
  MAX(CASE WHEN pm.meta_key = '_yoast_wpseo_title' THEN pm.meta_value END) AS seo_title,
  MAX(CASE WHEN pm.meta_key = '_yoast_wpseo_metadesc' THEN pm.meta_value END) AS seo_description,
  MAX(CASE WHEN pm.meta_key = '_yoast_wpseo_opengraph-image' THEN pm.meta_value END) AS og_image
FROM wp_posts p
JOIN wp_postmeta pm ON p.ID = pm.post_id
WHERE p.post_status = 'publish'
GROUP BY p.ID, p.post_name;

对于RankMath,交换元键:rank_math_titlerank_math_descriptionrank_math_facebook_image

将此数据导入到你的Supabaseposts表中我们之前定义的SEO列。

Next.js Metadata API

对于App Router,元数据是一流的:

// app/blog/[slug]/page.tsx
import { supabase } from '@/lib/supabase';
import { Metadata } from 'next';

export async function generateMetadata(
  { params }: { params: { slug: string } }
): Promise<Metadata> {
  const { data: post } = await supabase
    .from('posts')
    .select('title, seo_title, seo_description, og_image')
    .eq('slug', params.slug)
    .single();

  return {
    title: post.seo_title || post.title,
    description: post.seo_description,
    openGraph: {
      title: post.seo_title || post.title,
      description: post.seo_description,
      images: post.og_image ? [{ url: post.og_image }] : [],
    },
  };
}

作为JSON-LD的架构标记服务器组件

WordPress插件自动生成架构。在Next.js中,你自己构建——这实际上给你更多的控制权:

// components/ArticleSchema.tsx
export function ArticleSchema({ post }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    datePublished: post.published_at,
    dateModified: post.updated_at || post.published_at,
    author: {
      '@type': 'Organization',
      name: 'Your Company',
    },
    image: post.og_image,
    description: post.seo_description,
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  );
}

动态网站地图

// app/sitemap.ts
import { supabase } from '@/lib/supabase';

export default async function sitemap() {
  const { data: posts } = await supabase
    .from('posts')
    .select('slug, published_at')
    .eq('status', 'publish');

  return posts.map((post) => ({
    url: `https://yoursite.com/blog/${post.slug}`,
    lastModified: post.published_at,
    changeFrequency: 'monthly',
    priority: 0.8,
  }));
}

这在构建时为静态网站生成或按需为动态网站生成。没有插件、没有XML模板文件、没有缓存问题。

表单:Gravity Forms到Server Actions

Gravity Forms是有史以来最好的WordPress插件之一。它也是每年$259的Elite许可证,每个表单加载200KB+的JavaScript。

以下是替代方案。每个表单大约20行代码。

导出现有条目

首先,从WordPress管理员导出你的Gravity Forms条目为CSV。如果需要,将其存储在Supabase中用于历史记录。

Server Action联系表单

// app/contact/page.tsx
export default function ContactPage() {
  async function submitForm(formData: FormData) {
    'use server';

    const { createClient } = await import('@supabase/supabase-js');
    const supabase = createClient(
      process.env.SUPABASE_URL!,
      process.env.SUPABASE_SERVICE_KEY!
    );

    const entry = {
      name: formData.get('name') as string,
      email: formData.get('email') as string,
      message: formData.get('message') as string,
      submitted_at: new Date().toISOString(),
    };

    // Validate
    if (!entry.name || !entry.email || !entry.message) {
      throw new Error('All fields required');
    }

    await supabase.from('form_submissions').insert(entry);

    // Optional: send notification email via Resend
    await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        from: 'forms@yoursite.com',
        to: 'team@yoursite.com',
        subject: `New contact: ${entry.name}`,
        html: `<p>${entry.message}</p><p>From: ${entry.email}</p>`,
      }),
    });
  }

  return (
    <form action={submitForm}>
      <input name="name" type="text" required placeholder="Name" />
      <input name="email" type="email" required placeholder="Email" />
      <textarea name="message" required placeholder="Message" />
      <button type="submit">Send</button>
    </form>
  );
}

没有插件。表单本身没有JavaScript载荷(它是带有server action的本地HTML表单)。渐进式增强——即使在禁用JavaScript的情况下也能工作。

对于更复杂的表单(多步骤、文件上传、条件字段),我们在客户端使用React Hook Form与相同的server action模式。关键的认识:当你有数据库和API时,你不需要表单插件

WooCommerce到Stripe

这是任何WordPress迁移中最难的部分。WooCommerce不仅仅是一个插件——它是一个有产品、变体、库存、订单、订阅、优惠券、税收和运费规则的商务平台。你不是在迁移功能。你是在替换平台。

如何将WooCommerce产品迁移到Stripe?

通过WooCommerce REST API或CSV导出产品,然后使用Stripe Products API在Stripe中创建匹配的产品,价格。对于500个产品以下的网站,直接推送到Stripe使用他们的API:创建包含名称、描述和图像的产品,然后创建链接到该产品的价格对象。在Stripe元数据中存储WooCommerce产品ID作为参考。使用Stripe Checkout Sessions进行支付处理和webhooks以在你的数据库中跟踪订单。

产品迁移

通过CSV或REST API从WooCommerce导出产品。你有两个目标选项:

方法 适合 权衡
Supabase产品表 自定义店面、复杂过滤 你管理库存逻辑
Stripe Products API 简单目录、订阅业务 Stripe管理定价,你管理显示

对于大多数500个产品以下的网站,我直接推送到Stripe Products:

import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

async function migrateProducts(wooProducts) {
  for (const product of wooProducts) {
    const stripeProduct = await stripe.products.create({
      name: product.name,
      description: product.short_description,
      images: [product.images[0]?.src].filter(Boolean),
      metadata: {
        woo_id: String(product.id),
        slug: product.slug,
        sku: product.sku,
      },
    });

    await stripe.prices.create({
      product: stripeProduct.id,
      unit_amount: Math.round(parseFloat(product.price) * 100),
      currency: 'usd',
    });

    console.log(`Created: ${product.name} → ${stripeProduct.id}`);
  }
}

使用Stripe Checkout Sessions的结账

// app/api/checkout/route.ts
import { NextResponse } from 'next/server';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const { priceId, quantity = 1 } = await request.json();

  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    payment_method_types: ['card'],
    line_items: [{ price: priceId, quantity }],
    success_url: `${process.env.NEXT_PUBLIC_URL}/order/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/shop`,
  });

  return NextResponse.json({ url: session.url });
}

订阅

如果你在WooCommerce Subscriptions上($239/年),切换到Stripe Billing。将mode: 'payment'改为mode: 'subscription'并确保你的价格设置了recurring。就这样。Stripe处理试用期、按比例分配和催收。

通过Webhooks进行订单跟踪

// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import Stripe from 'stripe';
import { supabase } from '@/lib/supabase';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const body = await request.text();
  const sig = headers().get('stripe-signature')!;

  const event = stripe.webhooks.constructEvent(
    body,
    sig,
    process.env.STRIPE_WEBHOOK_SECRET!
  );

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object as Stripe.Checkout.Session;

    await supabase.from('orders').insert({
      stripe_session_id: session.id,
      customer_email: session.customer_details?.email,
      amount_total: session.amount_total,
      status: 'completed',
    });
  }

  return new Response('OK', { status: 200 });
}

Stripe的交易费为2.9% + $0.30每笔交易。与WooCommerce相比,你需要支付托管费用($30-100/月用于托管WP)、Subscriptions插件($239/年)、支付网关插件以及可能还有一些其他插件。数学运算快速得出结论。

对于复杂的商务迁移,我们将其作为Next.js开发服务的一部分提供——这是我们收到的最常见的请求之一。

迁移后监控

启动不是结束。迁移后的前两周是关键。

Google Search Console

  • 立即提交你的新网站地图
  • 使用URL Inspection工具请求索引你的前20个页面
  • 第一周每天监控Coverage报告——关注404s的峰值
  • 检查"页面索引"报告中任何卡在"已发现——目前未编制索引"的页面

分析比较

设置一个仪表板来逐周比较:

  • 总会话
  • 特别是有机搜索流量
  • 按页面的跳出率
  • 转换率(表单提交、购买)

第一周的小幅流量下降是正常的。如果到第三周还没有恢复,说明重定向或索引出了问题。

Lighthouse审计

在每个主要模板(主页、博客文章、产品页面、联系页面)上运行Lighthouse。目标:

  • 性能:90+
  • 可访问性:95+
  • 最佳实践:95+
  • SEO:100

在我们上次迁移——一个400页的内容网站——上,我们将WordPress上的平均Lighthouse性能评分从38提高到Vercel上Next.js的96。那不是樱桃精选。那是平均值。

何时保留WordPress

这是我失去一些人的部分,但这是本指南中最重要的部分。

如果出现以下情况,请不要迁移:

  • 你有一个简单的博客或宣传册网站,不到20页
  • 你的团队是非技术性的,依赖于WordPress管理员进行日常更新
  • 你的Lighthouse评分已经是70+,没有性能关键的业务需求
  • 你没有安全问题,托管是稳定的
  • 你的总插件成本在$200/年以下
  • 你没有开发人员(或预算)来维护Next.js网站

带有好主机(Cloudways、Kinsta)、可靠的主题和最少插件的WordPress很好。实际上,它超过了好——它经过战斗测试,文档齐全,被数百万开发人员理解。

迁移在以下情况下有意义:

  • 性能与收入直接相关(电子商务、SaaS营销网站)
  • 你在托管和安全插件上花费$500+/月
  • 你的开发团队已经在写React
  • 你需要一个部署管道,包括预览构建、暂存环境和回滚
  • 安全表面积是一个真实的关切(政府、医疗、金融)

我这样说是因为信任比销售更重要。如果你不确定迁移是否值得,联系我们,我们会给你一个诚实的评估。

性能基准:迁移前后

来自我们在2024-2025年进行的最后五次迁移:

指标 WordPress(平均) Next.js(平均) 变化
TTFB 1,200ms 85ms 快14倍
LCP 3.8s 0.9s 快4.2倍
总页面权重 3.2 MB 620 KB 轻5倍
每页请求 47 11 减少77%
Lighthouse性能 42 94 +52点
月托管成本 $75 $20(Vercel Pro) 节省73%
Core Web Vitals通过率 31%的页面 100%的页面

这些是来自生产网站的真实数字。WordPress网站运行在托管主机(WP Engine和Kinsta)上,优化缓存和图像优化插件。它们没有被忽视——它们是已经达到WordPress可以提供的上限的维护网站。

如果你有兴趣了解现代框架可能发生什么,也可以看看我们的Astro开发能力——对于内容丰富、交互最少的网站,Astro甚至可以提供比Next.js更小的载荷。

常见问题

从WordPress迁移到Next.js需要多长时间?

对于一个有100-500页的典型网站,预计4-8周的开发时间。简单的宣传册网站可以在2-3周内完成。有数千种产品的复杂WooCommerce商店可能需要10-12周。内容迁移本身很快——重建前端模板和测试每个重定向才是耗时的。

从WordPress迁移到Next.js时会失去SEO排名吗?

不会,如果你正确处理重定向和元数据。关键部分是:每个旧URL的301重定向、迁移所有Yoast/RankMath元标题和描述、保留你的网站地图结构以及立即向Google Search Console提交新网站地图。我们看到网站在迁移后1-2周内恢复到迁移前的流量,由于改进的Core Web Vitals,在第三个月到达时有显著增长。

我可以将WordPress与Next.js作为无头CMS一起使用吗?

是的,这是一种流行的方法。你保留WordPress作为内容后端,使用WP REST API或WPGraphQL,而Next.js作为前端。这保留了熟悉的编辑体验,同时获得Next.js性能。缺点是你仍在维护一个WordPress安装,有其安全和更新开销。我们通常为新项目推荐Payload CMS或Sanity,除非团队深入投入于WordPress工作流。

从WordPress迁移到Next.js需要多少成本?

DIY与开发人员时间:工具中免费,但预算80-200小时的开发时间。机构成本:通常$10,000-$50,000,取决于网站复杂性、页面数量、电子商务功能和自定义功能。查看我们的定价页面以了解我们包裹的具体情况。ROI通常来自减少的托管成本($50-100/月的节省)、消除的插件许可费和更好的性能增加的转换率。

迁移后WordPress插件会发生什么?

每个插件需要一个Next.js等效项。Contact Form 7或Gravity Forms变成Server Action。Yoast SEO变成Next.js Metadata API。WooCommerce变成Stripe。Google Analytics保持不变(只需移动跟踪代码片段)。Wordfence之类的一些插件变得不必要,因为没有要攻击的WordPress。在开始前制作一个完整的插件清单——任何没有明确替换策略的插件都是一个风险。

我应该从WordPress迁移到Next.js还是Astro?

这取决于你的交互性需求。Next.js更适合具有动态功能的网站——用户身份验证、电子商务、仪表板、实时数据。Astro更适合内容丰富的网站,主要是静态的——博客、文档、营销网站。Astro默认不发送JavaScript,这意味着更小的页面大小。我们与两者都合作——查看我们的Astro开发Next.js开发页面以了解详细信息。

我可以将WooCommerce订阅迁移到Stripe吗?

是的,但需要仔细处理活跃订户。你需要在Stripe中创建客户和订阅,然后向客户传达账单变化。Stripe Billing处理试用期、按比例分配、失败支付重试逻辑和取消流程。迁移本身是一个一次性脚本,但针对真实订阅情景的测试是耗时的地方。如果你有超过100个活跃订户,预算额外时间。

迁移WordPress之后Next.js的最佳托管是什么?

Vercel是默认选择——它由制作Next.js的团队构建,免费层处理大多数营销网站。Vercel Pro是$20/月的团队。替代选项包括Netlify、Cloudflare Pages(边缘性能优异)以及自托管,如果你想完全控制,可以在VPS上使用Docker。所有这些的成本与等效流量级别的托管WordPress托管相比都明显便宜。