TL;DR

La migración desde WordPress a Next.js en 2026 ofrece ganancias de rendimiento medibles: el TTFB promedio cae de 1,200 ms a 85 ms, el peso de la página se reduce de 3.2 MB a 620 KB, y las puntuaciones de Lighthouse saltan de 42 a 94. El proceso implica exportar contenido a través de WP REST API a Supabase o Payload CMS, migrar medios a almacenamiento de objetos, asignar cada URL para redirecciones 301, preservar datos SEO de Yoast/RankMath en Next.js Metadata API, y reemplazar plugins como Gravity Forms con Server Actions. Para sitios WooCommerce, Stripe reemplaza la pila de comercio completa. Espera 4-8 semanas para sitios típicos con 100-500 páginas, siendo la mayor inversión de tiempo en pruebas de redirección y reconstrucción de plantillas.

Esto no es una opinión provisional. He estado construyendo en WordPress durante más de 12 años. He lanzado sitios de agencia, plataformas de membresía, tiendas WooCommerce con ingresos mensuales de seis cifras, y más tipos de publicación personalizados de lo que puedo recordar. También he migrado sitios de producción a Next.js + Supabase. Aquí están todos los detalles técnicos -- qué se asigna limpiamente, qué no, y qué planificar.

No voy a pretender que WordPress sea malo. No lo es. Alimenta el 43% de la web por una buena razón. Pero para ciertos proyectos -- sitios donde el rendimiento es una métrica empresarial, donde el área de superficie de seguridad importa, donde quieres controlar tu tubería de implementación -- Next.js es la herramienta mejor. Sin embargo, la migración es un campo minado si no la planificas.

Esta guía cubre el proceso exacto que utilizo, con código real, gotchas reales y evaluaciones honestas de lo que ganarás y lo que perderás.

Tabla de Contenidos

WordPress to Next.js Migration: A Complete Technical Guide

Migración de Contenido: WP REST API a Supabase o Payload CMS

Cada migración de WordPress comienza aquí. Tienes posts, páginas, tipos de publicación personalizados, campos ACF, taxonomías -- años de contenido que necesitan estar en algún lugar seguro.

Tienes dos opciones sólidas para donde va ese contenido:

  • Supabase -- si quieres una base de datos que controles completamente, con seguridad a nivel de fila y una API REST/GraphQL lista para usar
  • Payload CMS -- si tu cliente necesita una experiencia de edición visual que se sienta familiar después de WordPress

Para nuestros proyectos de desarrollo de CMS headless, evaluamos esto caso por caso. Payload gana cuando los editores necesitan autoservicio. Supabase gana cuando los desarrolladores son los gestores de contenido principales o cuando necesitas los datos para más que solo un sitio web.

¿Qué estructura de contenido debo preservar al migrar de WordPress a Next.js?

Preserva metadatos de publicación, taxonomías, campos personalizados y slugs de URL durante la migración. Tus publicaciones de WordPress contienen años de datos estructurados: categorías, etiquetas, campos ACF, imágenes destacadas y fechas de publicación. Exporta todo esto a través de WP REST API con el parámetro _embed para obtener URLs de medios en una sola solicitud. Almacena tanto versiones HTML como Markdown del contenido -- HTML como respaldo, Markdown para renderizado MDX. Asigna tipos de publicación personalizados a tablas de base de datos o colecciones CMS equivalentes en tu nuevo sistema.

El Script de Exportación

Aquí está el script de Node.js que utilizo para extraer contenido de la WP REST API, limpiarlo e insertarlo en Supabase. Esto maneja posts, pero duplicarías el patrón para páginas y CPTs (solo cambia el endpoint).

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();

Algunas cosas que aprendí de la manera difícil:

  • Siempre usa _embed en tus llamadas a WP REST API. Sin él, obtienes IDs de medios en lugar de URLs, lo que significa N+1 solicitudes para resolver imágenes destacadas.
  • Turndown convierte HTML a Markdown -- esto es crítico si planeas renderizar con MDX más tarde. Mantén el HTML original también, como respaldo.
  • Los shortcodes no sobreviven. WordPress renderiza algunos shortcodes a través de REST API, pero muchos (especialmente de plugins como WPBakery o Elementor) llegan como texto de corchetes sin procesar. Necesitas una estrategia de mapeo de shortcode a componente. Mantengo una hoja de cálculo.
  • ACF / Campos Personalizados: Si estás usando ACF, necesitarás tener habilitado el plugin ACF to REST API, luego los campos personalizados aparecen en la propiedad acf de cada objeto de publicación.

Esquema de Tabla de 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()
);

Notarás que he incluido columnas seo_title, seo_description y og_image. Las necesitarás en la sección de migración de SEO a continuación.

Migración de Medios: De wp-content a Supabase Storage

Esta es la parte que la mayoría de las guías ignoran, y es la parte que toma más tiempo. Un sitio de WordPress de 12 años fácilmente puede tener 10,000+ archivos en wp-content/uploads/.

El enfoque:

  1. Descarga el directorio completo wp-content/uploads/
  2. Carga a Supabase Storage (o Cloudflare R2, o S3)
  3. Reescribe cada URL de medios en tu contenido

¿Cómo migro archivos de medios de WordPress a almacenamiento de objetos moderno?

Descarga tu directorio completo wp-content/uploads/, carga a Supabase Storage o Cloudflare R2, luego reescribe todas las URLs de medios en tu contenido. Usa un script para buscar cada URL de imagen de tu contenido de WordPress, carga a almacenamiento de objetos preservando la estructura de directorio (2024/03/image.jpg), luego ejecuta una segunda pasada para reemplazar URLs antiguas con nuevas URLs de almacenamiento. Configura redirecciones comodín para sitios externos que enlazan directamente a tus URLs de imagen antiguas.

Script de Descarga y Carga

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);
  }
}

La Victoria del Rendimiento de Imágenes

Aquí es donde la migración realmente se paga. WordPress sirve cargas originales -- a menudo PNGs de 3000×2000px que alguien subió desde su DSLR. Incluso con un plugin como ShortPixel, aún estás sirviendo imágenes a través de PHP.

El componente <Image> de Next.js con next/image realiza negociación automática de formato (WebP/AVIF), cambio de tamaño responsivo y carga perezosa. Los números de nuestra última migración:

Métrica WordPress Next.js + Image Component
Peso promedio de imagen por página 2.1 MB 380 KB
Solicitudes de imagen 12 por página 6 por página (carga perezosa)
Formato JPEG/PNG WebP (AVIF donde es compatible)
Cumulative Layout Shift 0.18 0.02

Eso no es un error tipográfico. La carga útil promedio de imagen se redujo de 2.1 MB a 380 KB. Y esto fue sin recargar archivos optimizados -- solo dejando que next/image haga su trabajo.

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,..." // generate at build time
    />
  );
}

Estructura de URL: Asignando Cada URL Antigua

Aquí es donde las migraciones mueren. Un redireccionamiento faltante significa un 404 para una página que Google ha indexado durante años. Trato el mapeo de URL con la misma seriedad que una migración de base de datos -- pruébalo, verifica, luego verifica de nuevo.

¿Cuál es la forma correcta de manejar redirecciones de URL durante la migración de WordPress?

Exporta cada URL publicada de WordPress, haz referencia cruzada con URLs indexadas de Google Search Console, luego implementa redirecciones 301 para todas ellas. Consulta tu tabla wp_posts para todas las URLs publicadas, exporta las URLs indexadas de GSC, y crea un mapa de redirección. Usa redirecciones en next.config.js para menos de 50 URLs, un archivo JSON para 50-1,024 URLs, o middleware para sitios que excedan el límite de 1,024 redirecciones de Vercel. Incluye redirecciones comodín para páginas de categoría, paginación y rutas wp-content/uploads.

El Proceso de Mapeo

Primero, exporta cada URL de WordPress. Obtengo de la base de datos directamente:

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;

Luego haz referencia cruzada con las URLs indexadas de Google Search Console. GSC a menudo muestra URLs que ya no existen en tu base de datos -- páginas de categoría antiguas, URLs de paginación, páginas de adjuntos. Necesitas redirecciones para todas ellas.

Redirecciones en next.config.js

Para sitios con menos de 50 redirecciones, inclúyelas en línea:

// 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,
      },
    ];
  },
};

Para 200+ Redirecciones: Usa un Archivo JSON

Una vez que pasas un par de cientos, mantener redirecciones en línea es miserable. Uso un archivo 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;
  },
};

La redirección comodín para wp-content/uploads es crítica. Habrá sitios externos que enlazan directamente a tus imágenes. No pierdas esos backlinks.

Importante: Vercel tiene un límite de 1,024 redirecciones en next.config.js. Para sitios con más de eso, usa middleware:

// 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 to Next.js Migration: A Complete Technical Guide - architecture

Migración de SEO: Datos de Yoast y RankMath a Next.js Metadata

Si has estado usando Yoast o RankMath, tienes años de títulos meta personalizados, descripciones y datos de Open Graph almacenados en la tabla wp_postmeta. No los pierdas.

¿Cómo preservo datos de SEO de Yoast al migrar a Next.js?

Exporta títulos meta de Yoast, descripciones e imágenes de Open Graph desde wp_postmeta, almacénalos en tu nueva base de datos, luego renderízalos usando Next.js Metadata API. Consulta wp_postmeta para campos _yoast_wpseo_title, _yoast_wpseo_metadesc y _yoast_wpseo_opengraph-image. Importa estos datos en columnas SEO dedicadas en tu tabla de posts. Usa generateMetadata en Next.js App Router para renderizar estos datos como etiquetas meta adecuadas y marcado de Open Graph.

Exportando Datos de 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;

Para RankMath, reemplaza las claves meta: rank_math_title, rank_math_description, rank_math_facebook_image.

Importa estos datos en la tabla posts de Supabase en las columnas de SEO que definimos anteriormente.

Next.js Metadata API

Con el App Router, metadata es un ciudadano de primera clase:

// 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 }] : [],
    },
  };
}

Marcado de Schema como Componentes de Servidor JSON-LD

Los plugins de WordPress generan schema automáticamente. En Next.js, lo construyes tú mismo -- que en realidad te da más control:

// 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) }}
    />
  );
}

Mapas de Sitio Dinámicos

// 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,
  }));
}

Esto se genera en tiempo de construcción para sitios estáticos o bajo demanda para dinámicos. Sin plugin, sin archivos de plantilla XML, sin problemas de almacenamiento en caché.

Formularios: Gravity Forms a Server Actions

Gravity Forms es uno de los mejores plugins de WordPress jamás creados. También cuesta $259/año por la licencia Elite, y cada formulario carga 200 KB+ de JavaScript.

Aquí está el reemplazo. Son aproximadamente 20 líneas de código por formulario.

Exportar Entradas Existentes

Primero, exporta tus entradas de Gravity Forms como CSV desde el administrador de WordPress. Almacénalas en Supabase para registros históricos si es necesario.

Formulario de Contacto de 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>
  );
}

Sin plugin. Sin carga útil de JavaScript para el formulario en sí (es un formulario HTML nativo con una acción de servidor). Mejora progresiva -- funciona sin JavaScript habilitado.

Para formularios más complejos (multipaso, cargas de archivos, campos condicionales), usamos React Hook Form en el lado del cliente con el mismo patrón de acción de servidor. La conclusión clave: no necesitas un plugin de formulario cuando tienes una base de datos y una API.

WooCommerce a Stripe

Esta es la parte más difícil de cualquier migración de WordPress. WooCommerce no es solo un plugin -- es una plataforma de comercio con productos, variaciones, inventario, pedidos, suscripciones, cupones, impuestos y reglas de envío. No estás migrando una característica. Estás reemplazando una plataforma.

¿Cómo migro productos de WooCommerce a Stripe?

Exporta productos a través de WooCommerce REST API o CSV, luego crea productos correspondientes en Stripe Products API con precios. Para sitios con menos de 500 productos, envía directamente a Stripe usando su API: crea un producto con nombre, descripción e imágenes, luego crea un objeto de precio vinculado a ese producto. Almacena el ID de producto de WooCommerce en metadatos de Stripe para referencia. Usa Stripe Checkout Sessions para procesamiento de pagos y webhooks para rastrear pedidos en tu base de datos.

Migración de Productos

Exporta productos de WooCommerce a través de CSV o REST API. Tienes dos opciones de destino:

Enfoque Mejor Para Tradeoffs
Tabla de productos de Supabase Escaparates personalizados, filtrado complejo Tú administras la lógica de inventario
Stripe Products API Catálogos simples, negocios basados en suscripciones Stripe gestiona precios, tú gestiona la pantalla

Para la mayoría de sitios con menos de 500 productos, envío directamente a 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}`);
  }
}

Pago con 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 });
}

Suscripciones

Si estás en WooCommerce Subscriptions ($239/año), cambia a Stripe Billing. Cambia mode: 'payment' a mode: 'subscription' y asegúrate de que tus precios tengan recurring configurado. Eso es todo. Stripe maneja períodos de prueba, prorrateo y recuperación de pagos fallidos.

Rastreo de Pedidos a través de 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 });
}

Las tarifas de transacción de Stripe son 2.9% + $0.30 por transacción. Compara eso con WooCommerce donde estás pagando por alojamiento ($30-100/mes para WordPress administrado), el plugin Subscriptions ($239/año), un plugin de pasarela de pago, y probablemente algunos complementos más. Las matemáticas se resuelven rápidamente.

Para migraciones de comercio complejas, ofrecemos esto como parte de nuestros servicios de desarrollo de Next.js -- es una de las solicitudes más comunes que recibimos.

Monitoreo Post-Migración

El lanzamiento no es el final. Las primeras dos semanas después de la migración son críticas.

Google Search Console

  • Envía tu nuevo mapa de sitio inmediatamente
  • Usa la herramienta de Inspección de URL para solicitar indexación de tus 20 páginas principales
  • Monitorea el informe de Cobertura diariamente durante la primera semana -- observa picos en 404s
  • Verifica el informe de "Indexación de páginas" para cualquier página atrapada en "Descubierta -- actualmente no indexada"

Comparación de Análisis

Configura un panel que compare semana a semana:

  • Sesiones totales
  • Tráfico de búsqueda orgánica específicamente
  • Tasa de rebote por página
  • Tasa de conversión (envíos de formularios, compras)

Una pequeña caída de tráfico en la primera semana es normal. Si no se ha recuperado para la tercera semana, algo salió mal con redirecciones o indexación.

Auditorías de Lighthouse

Ejecuta Lighthouse en cada plantilla principal (página de inicio, publicación de blog, página de producto, página de contacto). Objetivo:

  • Rendimiento: 90+
  • Accesibilidad: 95+
  • Mejores Prácticas: 95+
  • SEO: 100

En nuestra última migración -- un sitio de contenido de 400 páginas -- pasamos de una puntuación promedio de Lighthouse de rendimiento de 38 en WordPress a 96 en Next.js implementado en Vercel. Eso no es seleccionado cuidadosamente. Eso es el promedio.

Cuándo Permanecer en WordPress

Aquí es donde pierdo a algunos de ustedes, pero es la sección más importante de esta guía.

No migres si:

  • Tienes un blog simple o sitio de folleto con menos de 20 páginas
  • Tu equipo no es técnico y depende del administrador de WordPress para actualizaciones diarias
  • Tus puntuaciones de Lighthouse ya son 70+ y no tienes necesidades comerciales críticas de rendimiento
  • No tienes problemas de seguridad y tu alojamiento es estable
  • Tus costos totales de plugins son menores a $200/año
  • No tienes desarrollador (o presupuesto para uno) para mantener un sitio Next.js

WordPress con un buen host (Cloudways, Kinsta), un tema sólido y mínimos plugins está bien. Realmente, es más que bien -- está probado en batalla, bien documentado y entendido por millones de desarrolladores.

La migración tiene sentido cuando:

  • El rendimiento está directamente ligado a los ingresos (sitios de comercio electrónico, sitios de marketing de SaaS)
  • Estás gastando $500+/mes en alojamiento administrado y plugins de seguridad
  • Tu equipo de desarrollo ya está escribiendo React
  • Necesitas una tubería de implementación con compilaciones de vista previa, entornos de estadificación y reversiones
  • El área de superficie de seguridad es una preocupación genuina (gobierno, atención médica, finanzas)

Digo esto porque la confianza importa más que una venta. Si no estás seguro de si la migración es rentable, contáctanos y te daremos una evaluación honesta.

Puntos de Referencia de Rendimiento: Antes y Después

De nuestras últimas cinco migraciones en 2024-2025:

Métrica WordPress (Promedio) Next.js (Promedio) Cambio
TTFB 1,200 ms 85 ms 14x más rápido
LCP 3.8 s 0.9 s 4.2x más rápido
Peso Total de Página 3.2 MB 620 KB 5x más ligero
Solicitudes por Página 47 11 77% menos
Rendimiento de Lighthouse 42 94 +52 puntos
Costo de Alojamiento Mensual $75 $20 (Vercel Pro) 73% ahorro
Tasa de Aprobación de Core Web Vitals 31% de páginas 100% de páginas

Estos son números reales de sitios de producción. Los sitios de WordPress ejecutaban alojamiento administrado (WP Engine y Kinsta), almacenamiento en caché optimizado y plugins de optimización de imágenes. No fueron desatendidos -- fueron sitios mantenidos que simplemente habían alcanzado el techo de lo que WordPress puede entregar.

Si estás interesado en lo que es posible con frameworks modernos, también consulta nuestras capacidades de desarrollo de Astro -- para sitios con mucho contenido y mínima interactividad, Astro puede entregar cargas útiles aún más pequeñas que Next.js.

Preguntas Frecuentes

¿Cuánto tiempo toma una migración de WordPress a Next.js?

Para un sitio típico con 100-500 páginas, espera 4-8 semanas de tiempo de desarrollo. Los sitios simples de folleto pueden hacerse en 2-3 semanas. Las tiendas WooCommerce complejas con miles de productos podrían tomar 10-12 semanas. La migración de contenido en sí es rápida -- es la reconstrucción de plantillas de frontend y pruebas de cada redirección lo que toma tiempo.

¿Perderé clasificaciones de SEO al migrar de WordPress a Next.js?

No, si manejas redirecciones y metadatos correctamente. Las piezas críticas son: redirecciones 301 para cada URL antigua, migración de todos los títulos meta y descripciones de Yoast/RankMath, preservación de la estructura de tu mapa de sitio, y envío del nuevo mapa de sitio a Google Search Console inmediatamente. Hemos visto sitios recuperarse al tráfico anterior a la migración dentro de 1-2 semanas, con crecimiento orgánico significativo para el mes tres debido a mejores Core Web Vitals.

¿Puedo usar WordPress como un CMS headless con Next.js?

Sí, y es un enfoque popular. Mantienes WordPress como el backend de contenido, usando WP REST API o WPGraphQL, y Next.js como el frontend. Esto preserva la experiencia de edición familiar mientras obtienes el rendimiento de Next.js. La desventaja es que aún estás manteniendo una instalación de WordPress con su sobrecarga de seguridad y actualizaciones. Generalmente recomendamos Payload CMS o Sanity para proyectos nuevos a menos que el equipo esté profundamente invertido en flujos de trabajo de WordPress.

¿Cuánto cuesta migrar de WordPress a Next.js?

DIY con tiempo de desarrollador: gratis en herramientas, pero presupuesta 80-200 horas de tiempo de desarrollo. Costo de agencia: típicamente $10,000-$50,000 dependiendo de la complejidad del sitio, número de páginas, características de comercio electrónico y funcionalidad personalizada. Consulta nuestra página de precios para detalles específicos sobre nuestros paquetes. El ROI generalmente proviene del ahorro en costos de alojamiento ($50-100/mes de ahorros), tarifas de licencia de plugin eliminadas, y mayores tasas de conversión por mejor rendimiento.

¿Qué sucede con mis plugins de WordPress después de la migración?

Cada plugin necesita un equivalente de Next.js. Contact Form 7 o Gravity Forms se convierte en una Server Action. Yoast SEO se convierte en Next.js Metadata API. WooCommerce se convierte en Stripe. Google Analytics permanece igual (solo mueve el fragmento de seguimiento). Algunos plugins como Wordfence se vuelven innecesarios ya que no hay WordPress para atacar. Crea un inventario completo de tus plugins antes de comenzar -- cualquier plugin sin una estrategia de reemplazo clara es un riesgo.

¿Debo migrar a Next.js o Astro desde WordPress?

Depende de tus necesidades de interactividad. Next.js es mejor para sitios con características dinámicas -- autenticación de usuario, comercio electrónico, paneles de control, datos en tiempo real. Astro es mejor para sitios con mucho contenido que son mayormente estáticos -- blogs, documentación, sitios de marketing. Astro envía cero JavaScript por defecto, lo que significa cargas útiles de página aún más pequeñas. Trabajamos con ambos -- consulta nuestras páginas de desarrollo de Astro y desarrollo de Next.js para detalles.

¿Puedo migrar suscripciones de WooCommerce a Stripe?

Sí, pero requiere manejo cuidadoso de suscriptores activos. Necesitarás crear clientes y suscripciones en Stripe, luego comunicar el cambio de facturación a los clientes. Stripe Billing maneja períodos de prueba, prorrateo, lógica de reintento de pago fallido y flujos de cancelación. La migración en sí es un script de una sola vez, pero las pruebas contra escenarios de suscripción reales es donde va el tiempo. Presupuesta tiempo adicional si tienes más de 100 suscriptores activos.

¿Cuál es el mejor alojamiento para Next.js después de migrar desde WordPress?

Vercel es la opción predeterminada -- está construido por el equipo que crea Next.js, y el nivel gratuito maneja la mayoría de sitios de marketing. Vercel Pro es $20/mes para equipos. Las alternativas incluyen Netlify, Cloudflare Pages (excelente para rendimiento de borde), y auto-alojamiento con Docker en un VPS si quieres control total. Todos estos son significativamente más baratos que alojamiento de WordPress administrado para niveles de tráfico equivalentes.