Tu instalación de WordPress envía 487 kilobytes en cada carga de página — assets del tema, scripts de plugins, dependencias de jQuery apiladas en tres capas. Ves que las puntuaciones de rendimiento de Lighthouse se estancan en los 40 mientras los sitios en Next.js de tus competidores te superan con contenido idéntico. La migración a Next.js y Vercel suena limpia en teoría: exportar contenido, conectar un CMS headless, desplegar. En la práctica, he visto doce de estas migraciones durante tres años. Cuatro se desplegaron en menos de dos semanas. Ocho se estancaron durante meses porque los equipos se perdieron lo que WordPress manejaba silenciosamente — redirecciones, optimización de imágenes, mapas de sitio XML, inyección de metadatos — antes de eliminarlo. La diferencia entre un cambio sin problemas y una reversión a las 2 AM casi siempre depende de una cosa: auditar qué hace realmente WordPress en tu sitio antes de eliminar la instalación.

Esta guía es todo lo que desearía que alguien me hubiera dado antes de mi primera migración. Cubriremos el viaje completo: evaluar si deberías migrar, elegir tu CMS headless, mover contenido, reconstruir plantillas, manejar SEO sin perder rankings, e implementar en Vercel con una configuración que no falle bajo picos de tráfico.

Vamos a ello.

Migración de WordPress a Next.js en Vercel: Una Guía 2026

Tabla de Contenidos

¿Por qué migrar de WordPress a Next.js en 2026?

Seamos honesto — WordPress aún impulsa aproximadamente el 40% de la web en 2026. No va a desaparecer. Pero las razones para partir se han vuelto más convincentes:

Techo de rendimiento. Incluso con plugins de almacenamiento agresivo (WP Rocket, W3 Total Cache), la mayoría de los sitios de WordPress alcanzan un límite alrededor de 70-80 en las puntuaciones de rendimiento de Lighthouse. El bloat de plugins, PHP que bloquea el renderizado, y consultas de base de datos en cada carga de página crean una sobrecarga que ninguna cantidad de optimización puede eliminar completamente.

Área de superficie de seguridad. WordPress tuvo 149 vulnerabilidades documentadas en 2025 en el núcleo y plugins populares. Cada plugin es un vector de ataque. Cada actualización de tema es un cambio potencial. Si ejecutas WooCommerce, el área de superficie se duplica.

Experiencia del desarrollador. Si tu equipo conoce React, construir en plantillas PHP se siente como escribir con la mano no dominante. El App Router de Next.js 15, Server Components, y almacenamiento en caché integrado te dan un flujo de trabajo de desarrollo moderno que WordPress no puede igualar.

Costo a escala. El hosting de WordPress gestionado (WP Engine, Kinsta) cuesta $30-$300/mes para un rendimiento decente. El plan Pro de Vercel a $20/usuario/mes con funciones edge y escalado automático a menudo cuesta menos mientras funciona mejor.

Dicho esto — no migres solo porque sea tendencia. Si tu sitio es un blog simple con 50 posts y tu cliente lo actualiza semanalmente a través del administrador de WordPress, una migración podría crear más problemas de los que resuelve. Los mejores candidatos para migración son:

  • Sitios con 500+ páginas que necesitan mejor rendimiento
  • Equipos que quieren desarrollo basado en componentes
  • Sitios donde los conflictos de plugins están causando pesadillas de mantenimiento
  • Sitios de comercio electrónico que alcanzan los límites de rendimiento de WooCommerce
  • Sitios de marketing que necesitan pruebas A/B y personalización en el edge

Auditoría Previa a la Migración: Lo que WordPress Realmente Está Haciendo

Aquí es donde la mayoría de las migraciones salen mal. Las personas piensan que están migrando un blog, pero WordPress en realidad está manejando formularios de contacto, redirecciones, optimización de imágenes, búsqueda, comentarios, autenticación, trabajos cron, y quince otras cosas enterradas en configuraciones de plugins.

Antes de escribir una sola línea de código de Next.js, documenta todo:

Inventario de Plugins

Exporta tu lista de plugins y categoriza cada uno:

wp plugin list --status=active --format=csv > active-plugins.csv

Para cada plugin, responde: ¿Qué hace esto, y qué lo reemplaza en el ecosistema de Next.js?

Plugin de WordPress Función Reemplazo en Next.js
Yoast SEO Etiquetas meta, mapas de sitio, esquema next-seo + ruta sitemap.xml personalizada
WP Rocket Almacenamiento en caché, minificación Vercel Edge Cache + Next.js integrado
Contact Form 7 Manejo de formularios React Hook Form + ruta API o Formspree
Wordfence Seguridad No es necesario (sin superficie de administrador)
WPML Multilingüe next-intl o enrutamiento i18n integrado
WooCommerce Comercio electrónico Shopify Storefront API o Saleor
Advanced Custom Fields Modelos de contenido personalizado Modelado de contenido de tu CMS headless
Redirection Redirecciones de URL next.config.js redirects o config de Vercel
WP Cron Tareas programadas Vercel Cron Jobs o servicio separado
Imagify Optimización de imágenes next/image con Optimización de Imágenes de Vercel

Inventario de Contenido

Cuenta y categoriza tu contenido:

SELECT post_type, post_status, COUNT(*) 
FROM wp_posts 
GROUP BY post_type, post_status;

No olvides: tipos de posts personalizados, términos de taxonomía, perfiles de usuario, estructuras de menú, configuraciones de widgets, y valores de opciones. Ese sitio de WordPress "simple" probablemente tiene más tipos de contenido de lo que piensas.

Mapeo de URLs

Exporta cada URL. Cada una. Usa Screaming Frog o un rastreo simple de sitemap:

curl -s https://tusite.com/sitemap_index.xml | \
grep -oP '<loc>\K[^<]+' | \
xargs -I {} curl -s {} | \
grep -oP '<loc>\K[^<]+' > all-urls.txt

Este archivo es oro. Lo usarás para mapeo de redirecciones, preservación de SEO, y pruebas de QA después de la migración.

Migración de WordPress a Next.js en Vercel: Una Guía 2026 - arquitectura

Elegir tu Backend CMS Headless

Necesitas un lugar para almacenar y gestionar contenido. Los tres caminos más comunes en 2026:

Opción 1: WordPress como CMS Headless

Sí, puedes mantener WordPress como el backend y usar Next.js como el frontend. WPGraphQL (ahora en v2.1) hace esto sorprendentemente viable. Tus editores mantienen la interfaz de administrador familiar. Obtienes un frontend moderno.

// lib/wordpress.js
const API_URL = process.env.WORDPRESS_GRAPHQL_URL;

export async function getPostBySlug(slug) {
  const res = await fetch(API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: `
        query PostBySlug($slug: ID!) {
          post(id: $slug, idType: SLUG) {
            title
            content
            date
            featuredImage {
              node {
                sourceUrl
                altText
              }
            }
            seo {
              title
              metaDesc
              opengraphImage {
                sourceUrl
              }
            }
          }
        }
      `,
      variables: { slug },
    }),
    next: { revalidate: 60 },
  });
  const json = await res.json();
  return json.data.post;
}

¿La desventaja? Aún mantienes una instalación de WordPress. Actualizaciones de seguridad, gestión de versiones de PHP, respaldos de base de datos — todo sigue siendo tu responsabilidad. Y aún estás pagando por el hosting de WordPress.

Opción 2: CMS Headless de Propósito Específico

Esto es lo que recomiendo para la mayoría de las migraciones. Mueve tu contenido a un CMS que fue construido desde cero para distribución primero en API.

CMS Precios (2026) Mejor para Modelado de Contenido Tipo de API
Sanity Nivel gratuito, $15/usuario/mes Pro Contenido complejo, colaboración en tiempo real Excelente, definido en código GROQ + GraphQL
Contentful Nivel gratuito, €300/mes Team Empresa, equipos grandes Bueno, definido en UI REST + GraphQL
Storyblok Nivel gratuito, €106/mes Business Edición visual, componentes Excelente, visual REST + GraphQL
Strapi v5 Gratuito (autohospedado), Cloud desde $29/mes Control total, código abierto Flexible, definido en UI REST + GraphQL
Payload CMS 3.0 Gratuito (autohospedado) Desarrolladores que quieren código primero Excelente, definido en código REST + GraphQL

Si tu equipo en Social Animal está manejando la migración, típicamente recomendamos Sanity o Payload para desarrollo de CMS headless — dan a los desarrolladores el máximo control sobre el modelado de contenido manteniendo a los editores contentos.

Opción 3: Markdown/MDX en el Repositorio

Para blogs de desarrolladores y sitios de documentación, almacenar contenido como archivos MDX en tu repositorio de Git es el enfoque más simple. Sin CMS que gestionar, sin llamadas a API, contenido versionado junto con el código. Pero esto solo funciona si tus editores de contenido están cómodos con flujos de trabajo de Git.

Estrategia de Migración de Contenido

Exportando desde WordPress

La exportación integrada de WordPress (Herramientas → Exportar) te da un archivo XML. Es un comienzo, pero es desordenado. Para una migración estructurada, uso un script personalizado de WP-CLI:

// export-content.php
<?php
$posts = get_posts([
    'post_type' => ['post', 'page', 'your_custom_type'],
    'posts_per_page' => -1,
    'post_status' => 'publish',
]);

$export = [];
foreach ($posts as $post) {
    $export[] = [
        'id' => $post->ID,
        'title' => $post->post_title,
        'slug' => $post->post_name,
        'content' => apply_filters('the_content', $post->post_content),
        'excerpt' => $post->post_excerpt,
        'date' => $post->post_date,
        'modified' => $post->post_modified,
        'author' => get_the_author_meta('display_name', $post->post_author),
        'categories' => wp_get_post_categories($post->ID, ['fields' => 'names']),
        'tags' => wp_get_post_tags($post->ID, ['fields' => 'names']),
        'featured_image' => get_the_post_thumbnail_url($post->ID, 'full'),
        'acf' => function_exists('get_fields') ? get_fields($post->ID) : [],
        'yoast' => [
            'title' => get_post_meta($post->ID, '_yoast_wpseo_title', true),
            'description' => get_post_meta($post->ID, '_yoast_wpseo_metadesc', true),
        ],
    ];
}

file_put_contents('export.json', json_encode($export, JSON_PRETTY_PRINT));

Transformando Contenido

El contenido de WordPress se almacena como HTML con markup de bloques Gutenberg. Necesitarás decidir: ¿mantener el HTML y renderizarlo, o convertir al formato estructurado de tu CMS?

Para Sanity, uso @sanity/block-tools para convertir HTML a Portable Text. Para Contentful, su CLI de migración maneja la conversión de rich text. De cualquier forma, presupuesta tiempo para limpieza de contenido — el contenido de WordPress está lleno de [shortcodes], estilos en línea, e HTML roto que necesita ser limpiado.

// migrate-to-sanity.js
import { htmlToBlocks } from '@sanity/block-tools';
import { JSDOM } from 'jsdom';
import { Schema } from '@sanity/schema';

const schema = Schema.compile({ /* your schema */ });
const blockContentType = schema.get('post')
  .fields.find(f => f.name === 'body').type;

function convertPost(wpPost) {
  return {
    _type: 'post',
    title: wpPost.title,
    slug: { current: wpPost.slug },
    publishedAt: wpPost.date,
    body: htmlToBlocks(
      wpPost.content,
      blockContentType,
      { parseHtml: (html) => new JSDOM(html).window.document }
    ),
  };
}

Migración de Imágenes

No omitas esto. Descarga cada imagen de wp-content/uploads, vuelve a cargar en tu CMS o un CDN (Cloudinary, Vercel Blob Storage, S3), y actualiza todas las referencias de contenido. Típicamente escribo un script que:

  1. Rastrea el JSON de exportación para URLs de imágenes
  2. Descarga cada imagen
  3. Carga en el nuevo almacenamiento
  4. Crea un archivo de mapeo de URL
  5. Ejecuta búsqueda y reemplazo en todo el contenido

Reconstruyendo tu Frontend en Next.js 15

Next.js 15 (estable desde finales de 2024, con 15.2 actual en 2026) usa el App Router por defecto. Aquí está la estructura que uso para sitios con mucho contenido:

app/
├── layout.tsx          # Layout raíz con fuentes, analítica
├── page.tsx            # Página de inicio
├── blog/
│   ├── page.tsx        # Listado de blogs con paginación
│   └── [slug]/
│       └── page.tsx    # Posts individuales de blog
├── [slug]/
│   └── page.tsx        # Páginas genéricas
├── sitemap.ts          # Generación dinámica de sitemap
├── robots.ts           # robots.txt
└── not-found.tsx       # 404 personalizado

Generación Estática con ISR

Para la mayoría de las páginas de contenido, Regeneración Estática Incremental es el punto dulce — rendimiento estático con frescura dinámica:

// app/blog/[slug]/page.tsx
import { getPostBySlug, getAllPostSlugs } from '@/lib/cms';
import { notFound } from 'next/navigation';

export async function generateStaticParams() {
  const slugs = await getAllPostSlugs();
  return slugs.map((slug) => ({ slug }));
}

export async function generateMetadata({ params }) {
  const post = await getPostBySlug(params.slug);
  if (!post) return {};
  return {
    title: post.seo?.title || post.title,
    description: post.seo?.description || post.excerpt,
    openGraph: {
      images: [post.featuredImage?.url],
    },
  };
}

export const revalidate = 3600; // Revalidar cada hora

export default async function BlogPost({ params }) {
  const post = await getPostBySlug(params.slug);
  if (!post) notFound();

  return (
    <article className="prose lg:prose-xl">
      <h1>{post.title}</h1>
      <time dateTime={post.date}>{formatDate(post.date)}</time>
      <PostBody content={post.body} />
    </article>
  );
}

Para sitios que necesitan actualizaciones en tiempo real (noticias, contenido en vivo), usa revalidación bajo demanda a través de webhooks de tu CMS. La mayoría de las plataformas de CMS headless soportan disparadores de webhook en publicación de contenido.

Si estás mirando desarrollo de Next.js y quieres entender más profundamente los compromisos de renderizado, hemos escrito sobre eso por separado.

Estructura de URLs y Preservación de SEO

Esto es innegociable. Si pierdes tu estructura de URL, pierdes tus rankings de búsqueda. Punto.

Mapa de Redirecciones

WordPress usa patrones de URL como /2024/03/post-slug/ o /category/term/. Tu sitio en Next.js probablemente usa /blog/post-slug. Crea un mapa de redirecciones:

// next.config.js
module.exports = {
  async redirects() {
    return [
      // URLs basadas en fecha a slugs limpios
      {
        source: '/:year(\\d{4})/:month(\\d{2})/:slug',
        destination: '/blog/:slug',
        permanent: true,
      },
      // Archivos de categorías
      {
        source: '/category/:slug',
        destination: '/blog?category=:slug',
        permanent: true,
      },
      // Paginación
      {
        source: '/page/:num',
        destination: '/blog?page=:num',
        permanent: true,
      },
      // URLs de feed
      {
        source: '/feed',
        destination: '/rss.xml',
        permanent: true,
      },
    ];
  },
};

Para sitios grandes (1000+ redirecciones), usa la configuración vercel.json de Vercel o middleware en su lugar — las redirecciones de next.config.js tienen un límite suave de alrededor de 1024 entradas antes de que los tiempos de compilación comiencen a sufrir.

Datos Estructurados

Los plugins de WordPress como Yoast generan automáticamente JSON-LD. Necesitas replicar esto:

// components/structured-data.tsx
export function ArticleSchema({ post }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    datePublished: post.date,
    dateModified: post.modified,
    author: {
      '@type': 'Person',
      name: post.author,
    },
    image: post.featuredImage?.url,
    publisher: {
      '@type': 'Organization',
      name: 'Nombre de Tu Sitio',
      logo: { '@type': 'ImageObject', url: '/logo.png' },
    },
  };

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

Mapa de Sitio XML

Next.js 15 hace que los mapas de sitio dinámicos sean directos:

// app/sitemap.ts
import { getAllPosts, getAllPages } from '@/lib/cms';

export default async function sitemap() {
  const posts = await getAllPosts();
  const pages = await getAllPages();

  return [
    { url: 'https://tusite.com', lastModified: new Date() },
    ...pages.map((page) => ({
      url: `https://tusite.com/${page.slug}`,
      lastModified: page.modified,
    })),
    ...posts.map((post) => ({
      url: `https://tusite.com/blog/${post.slug}`,
      lastModified: post.modified,
    })),
  ];
}

Implementación en Vercel: Configuración que Realmente Funciona

Configuración del Proyecto

npx create-next-app@latest mi-sitio-migrado --typescript --tailwind --app
cd mi-sitio-migrado
vercel link

Variables de Entorno

Establece estas en el panel de Vercel, no en archivos .env comprometidos en Git:

CMS_API_URL=https://tu-endpoint-api-cms
CMS_API_TOKEN=tu-token-solo-lectura
REVALIDATION_SECRET=una-cadena-aleatoria-para-autenticacion-webhook
SITE_URL=https://tusite.com

Configuración de Vercel

// vercel.json
{
  "crons": [
    {
      "path": "/api/revalidate-sitemap",
      "schedule": "0 */6 * * *"
    }
  ],
  "headers": [
    {
      "source": "/fonts/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    }
  ]
}

Webhook de Revalidación Bajo Demanda

// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-revalidation-secret');
  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
  }

  const body = await request.json();
  const { slug, type } = body;

  if (type === 'post') {
    revalidatePath(`/blog/${slug}`);
    revalidatePath('/blog'); // Revalidar listado también
  } else {
    revalidatePath(`/${slug}`);
  }

  return NextResponse.json({ revalidated: true });
}

Apunta tu webhook del CMS a https://tusite.com/api/revalidate con el encabezado de secreto. Ahora las actualizaciones de contenido aparecen en segundos sin compilaciones completas.

Benchmarks de Rendimiento: Antes y Después

Estos son números reales de migraciones que completamos en Social Animal en 2025-2026:

Métrica WordPress (WP Engine) Next.js (Vercel) Mejora
Rendimiento de Lighthouse 62-78 95-100 +30-40%
Largest Contentful Paint 2.8-4.2s 0.8-1.4s 60-70% más rápido
Time to First Byte 800ms-1.5s 50-120ms 90%+ más rápido
Cumulative Layout Shift 0.12-0.25 0.01-0.05 ~80% de reducción
Costo de hosting mensual $115/mes promedio $20-40/mes Ahorro de 60-80%
Tiempo de compilación (500 páginas) N/A (dinámico) 45-90 segundos N/A
Páginas/segundo (ISR) 15-30 req/s 10,000+ desde edge Órdenes de magnitud

La mejora de TTFB por sí sola vale la migración. WordPress genera cada página a través de PHP y MySQL. Vercel sirve páginas pre-renderizadas desde nodos edge en 300+ ubicaciones en todo el mundo.

Errores Comunes en la Migración

Error 1: Olvidar los feeds RSS de WordPress. Si la gente se suscribe a tu feed, redirige /feed/ y /rss/ a un nuevo endpoint de RSS. Next.js no genera feeds por defecto — necesitas una ruta personalizada.

Error 2: Perder shortcodes de WordPress. El contenido exportado de WordPress está lleno de [gallery], [embed], y shortcodes específicos de plugins. Si no analizas y conviertes estos, se renderizan como texto sin formato. Escribe transformadores para cada tipo de shortcode que tu contenido use.

Error 3: Ignorar datos de comentarios de WordPress. Si tienes threads de comentarios valiosos, migralos a un servicio como Disqus o construye un sistema de comentarios personalizado. La mayoría de las personas simplemente eliminan los comentarios, pero verifica con los stakeholders primero.

Error 4: No probar enlaces internos. El contenido de WordPress está lleno de enlaces internos usando URLs absolutas (https://tusite.com/old-path/). Estos necesitan actualizarse a rutas relativas o tu nueva estructura de URL. Un simple reemplazo regex durante la migración maneja la mayoría de casos.

Error 5: Olvidar referencias de wp-content/uploads. Incluso después de migrar imágenes, el contenido antiguo puede hacer referencia a rutas /wp-content/uploads/2024/03/image.jpg. Configura una redirección catch-all o proxy estas a tu nuevo CDN de imágenes.

Si esto se siente abrumador, honestamente es normal. Una migración adecuada toma 4-12 semanas dependiendo de la complejidad del sitio. Revisa nuestro pricing o contáctanos directamente si quieres manos experimentadas en el proyecto.

Preguntas Frecuentes

¿Cuánto tiempo tarda una migración de WordPress a Next.js? Para un sitio con 100-500 páginas, espera 4-8 semanas con un desarrollador dedicado. Los sitios más grandes con tipos de posts personalizados, comercio electrónico, o contenido multilingüe pueden tomar 8-16 semanas. La migración de contenido en sí es generalmente el 20% del trabajo — el otro 80% es reconstruir plantillas, manejar casos extremos, y pruebas de QA en cada URL.

¿Perderé mis rankings de Google durante la migración? No si manejas las redirecciones correctamente. Implementa redirecciones 301 para cada URL que cambie, preserva tus títulos meta y descripciones, envía tu nuevo sitemap a Google Search Console, y usa la herramienta de Cambio de Dirección si tu dominio cambia. Espera una pequeña fluctuación de ranking durante 2-4 semanas, luego recuperación o mejora cuando Google reconozca mejor Core Web Vitals.

¿Puedo mantener WordPress como mi CMS con Next.js? Absolutamente. Esto se llama "WordPress headless" y es un enfoque popular. Usa WPGraphQL para exponer tu contenido como una API, luego conúmelo desde Next.js. Tus editores mantienen el administrador de WordPress que conocen. La principal desventaja es que aún mantienes una instalación de WordPress — actualizaciones de seguridad, hosting, la pila completa.

¿Cuánto cuesta migrar de WordPress a Next.js? Autohecha con un desarrollador: 100-300 horas de trabajo. Migración de agencia: típicamente $15,000-$75,000 dependiendo de la complejidad. Los costos de hosting en curso generalmente disminuyen — Vercel Pro a $20/usuario/mes versus hosting de WordPress gestionado a $50-$300/mes. El ROI viene de menores costos de hosting, mejor rendimiento (que mejora las tasas de conversión), y menor overhead de mantenimiento.

¿Debería usar el Pages Router o el App Router en Next.js 15? App Router, sin dudas. En 2026, el App Router es el defecto estable con Server Components, streaming, y rutas paralelas. El Pages Router aún funciona y no está deprecado, pero las nuevas características y optimizaciones son primero en App Router. Cada migración que hacemos en Social Animal usa el App Router exclusivamente. Revisa nuestras capacidades de desarrollo de Next.js para más sobre nuestro enfoque.

¿Qué hay de formularios y funcionalidad dinámica? Las rutas de API de Next.js (o Server Actions en el App Router) manejan envíos de formularios, búsqueda, autenticación, y cualquier lógica del lado del servidor. Para formularios de contacto, puedes usar Server Actions con un servicio como Resend para entrega de correo. Para búsqueda, considera Algolia o Meilisearch. Para autenticación, Next Auth.js (ahora Auth.js v5) cubre la mayoría de casos de uso.

¿Es Vercel la única opción para alojar Next.js? No. Puedes desplegar Next.js en Netlify, AWS Amplify, Cloudflare Pages, o autohospedarlo con Node.js. Sin embargo, Vercel fue construido por el equipo de Next.js, y la integración se muestra — ISR, middleware edge, optimización de imágenes, y analítica funcionan mejor en Vercel. La brecha de DX entre Vercel y alternativas se ha reducido en 2026, pero Vercel sigue siendo el camino de menor resistencia.

¿Qué pasa si necesito migrar una tienda WooCommerce? Este es un proyecto más grande. La mayoría de los equipos migran la tienda a Next.js mientras mueven el backend de comercio a Shopify (usando Storefront API), Medusa.js, o Saleor. El framework Hydrogen de Shopify es otra opción, pero si quieres control total sobre el frontend, Next.js con la API de Shopify es el camino más probado. Espera que la migración de comercio electrónico agregue 4-8 semanas a tu cronograma.