¡Hola! Envíame un mensaje
Contacto

Ciudad de México, México
Disponible para proyectos remotos y presenciales.

Redes profesionales

LinkedIn · GitHub · Email
Ingeniería aplicada, resultados medibles.

Migrar WordPress a React y Next.js sin perder SEO: buenas prácticas para migraciones enterprise

· 10 min de lectura · Samuel Torres

Curva de Lissajous 3:4
x(t) = sin(3t + pi/2), y(t) = sin(4t)

En resumen

  • El SEO se pierde en las URLs, no en el framework: inventaría todas antes de escribir código.
  • Cada URL vieja necesita un destino: 301 a su equivalente, nunca a la portada.
  • Título, descripción, canónica y hreflang se generan en el servidor con generateMetadata.
  • Lo indexable se renderiza estático o con ISR; el cliente solo agrega interacción.
  • Compara el HTML crudo antes y después, URL por URL, antes de cambiar el DNS.

Migrar WordPress a React sin perder SEO es, sobre todo, un problema de inventario. El framework importa menos de lo que parece. Lo que tira el tráfico orgánico son las URLs que dejan de existir, los títulos que se repiten y el contenido que solo aparece cuando corre JavaScript.

Trabajo en migraciones enterprise a React manteniendo el SEO. En este artículo comparto el proceso que sigo con Next.js y el App Router: qué se decide antes de escribir código, qué se configura en el framework y cómo se verifica que nada se perdió antes de cambiar el DNS.

Los ejemplos son ilustrativos. Muestran el patrón con dominios y rutas de ejemplo, no el código de ningún cliente.

Antes del código: el inventario de URLs

Un sitio en WordPress acumula años de URLs: entradas, páginas, categorías, etiquetas, archivos por fecha, adjuntos y paginación. Muchas reciben tráfico o enlaces externos aunque nadie las recuerde.

Si una de esas URLs responde 404 después de la migración, su posicionamiento se pierde. Por eso el primer entregable no es un componente. Es una lista completa de lo que existe hoy.

Las fuentes para armarla se combinan, porque ninguna está completa por sí sola:

  • Los sitemaps del sitio actual. WordPress los genera por tipo de contenido. Son el punto de partida.
  • Search Console. El informe de páginas muestra URLs con impresiones que a veces no están en el sitemap.
  • Los registros del servidor. Revelan URLs viejas que todavía reciben visitas o enlaces desde otros sitios.
  • Un rastreo completo. Encuentra lo que está enlazado internamente aunque no esté en ningún sitemap.

El primer paso se automatiza en pocas líneas. Este script extrae todas las URLs de los sitemaps de WordPress a un archivo plano:

#!/usr/bin/env bash
# Inventario: todas las URLs que WordPress declara hoy, una por línea.
SITIO="https://www.ejemplo.com"
: > urls-actuales.txt

# El índice apunta a un sitemap por tipo de contenido (posts, pages, categorías…).
for mapa in $(curl -s "$SITIO/wp-sitemap.xml" | grep -o '<loc>[^<]*' | sed 's/<loc>//'); do
  curl -s "$mapa" | grep -o '<loc>[^<]*' | sed 's/<loc>//' >> urls-actuales.txt
done

sort -u urls-actuales.txt -o urls-actuales.txt   # sin duplicados: cada URL es un caso
wc -l urls-actuales.txt

Con la lista lista, cada URL recibe una decisión: se conserva igual, se redirige a su equivalente o se retira a propósito. No hay cuarta opción. Una URL sin decisión es una URL que se va a perder.

Conservar las URLs idénticas siempre es la opción más segura. Si la estructura nueva lo permite, replica los slugs de WordPress y ahórrate la mayoría de las redirecciones.

Buenas prácticas de Next.js para migraciones enterprise

Redirecciones 301: un mapa, no una regla genérica

Las URLs que cambian necesitan una redirección permanente a su equivalente exacto. Redirigir todo a la portada es casi tan malo como un 404: el buscador lo trata como contenido perdido.

Para un mapa fijo, la opción más limpia en Next.js es la función redirects de la configuración. Se resuelve antes de renderizar y no depende de ningún componente:

// next.config.ts — el mapa sale del inventario, revisado por SEO y negocio.
import type { NextConfig } from "next";
import mapa from "./migracion/redirecciones.json"; // [{ origen, destino }]

const config: NextConfig = {
  async redirects() {
    // permanent: true responde 308, que los buscadores tratan como un 301.
    return mapa.map(({ origen, destino }) => ({ source: origen, destination: destino, permanent: true }));
  },
  trailingSlash: true, // igual que WordPress: evita duplicar cada URL con y sin diagonal
};
export default config;

Dos detalles importan. El primero es trailingSlash: WordPress usa la diagonal final por defecto, y si el sitio nuevo no la respeta, cada URL tiene dos versiones. El segundo es evitar cadenas: si una URL ya redirigía en WordPress, el mapa debe apuntar al destino final, no al intermedio.

El middleware entra cuando la regla depende de la petición: patrones con lógica, mapas muy grandes que viven en una base de datos o reglas por idioma. Para lo demás, el mapa declarativo es más fácil de revisar.

Metadatos generados en el servidor

En WordPress, un plugin de SEO arma el título, la descripción y la canónica de cada página. En Next.js ese trabajo lo hace generateMetadata, y lo hace en el servidor: los metadatos ya vienen en el HTML inicial.

Un detalle de Next.js 15: params ahora es una promesa. Hay que esperarla antes de usarla. El ejemplo vive en app/blog/[slug]/page.tsx:

import type { Metadata } from "next";
import { obtenerEntrada } from "@/lib/cms";
type Props = { params: Promise<{ slug: string }> }; // en Next.js 15, params es una promesa
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const e = await obtenerEntrada(slug); // la misma petición se reutiliza en la página
  return {
    title: e.tituloSeo ?? e.titulo,   // conserva el título que ya posiciona
    description: e.descripcionSeo,
    alternates: { canonical: `/blog/${slug}/`, languages: { en: `/en/blog/${e.slugEn}/` } },
  };
}

La regla de oro es conservar lo que ya funciona. Si una página posiciona con un título y una descripción concretos, esos textos se migran tal cual desde los campos del plugin de SEO. La migración no es el momento de reescribir títulos.

La canónica y el hreflang van en el mismo objeto. Si el sitio es bilingüe, cada versión declara a la otra, y ambas deben ser recíprocas. Configura metadataBase en el layout raíz para que las rutas relativas salgan como URLs absolutas.

Sitemap y robots como código

El App Router genera sitemap.xml y robots.txt desde archivos TypeScript. Eso permite que el sitemap salga de la misma fuente que las páginas y nunca quede desactualizado:

// app/sitemap.ts — sale de la misma fuente que las páginas: nunca se desfasa.
import type { MetadataRoute } from "next";
import { todasLasEntradas } from "@/lib/cms";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const entradas = await todasLasEntradas();
  return entradas
    .filter((e) => !e.noindex) // lo que no se indexa tampoco se anuncia
    .map((e) => ({
      url: `https://www.ejemplo.com/blog/${e.slug}/`,
      lastModified: e.modificado, // la fecha real de edición, no la del deploy
    }));
}

Un detalle que se olvida en los entornos de prueba: el staging debe bloquear la indexación con robots.ts y autenticación, y esa regla no debe viajar a producción. Más de una migración ha salido con Disallow: / en el sitio público.

Migración de WordPress a React: qué cambia en el renderizado

WordPress entrega HTML completo desde PHP en cada petición. Esa es la base contra la que se compara el sitio nuevo: todo lo que un rastreador veía antes debe seguir en el HTML inicial.

En Next.js eso se logra eligiendo bien el modo de renderizado de cada ruta:

  • Estático. Para páginas institucionales y contenido que cambia poco. Se genera al construir y se sirve desde caché. Es la opción más rápida.
  • Estático con revalidación (ISR). Para el blog y catálogos. La página se sirve desde caché y se regenera cada cierto tiempo, o cuando el CMS avisa que algo cambió.
  • Dinámico en el servidor. Para lo que depende de la petición: búsquedas, contenido personalizado.
  • Solo en el cliente. Para interacción que no necesita indexarse: filtros, carritos, paneles.

La regla práctica: si debe aparecer en un buscador, no puede depender de un efecto en el navegador. Ya escribí sobre ese error en detalle en errores comunes en useEffect; aquí basta con decir que el contenido principal se resuelve en componentes de servidor.

¿Y WordPress? Muchas migraciones lo conservan como CMS sin cabeza. Los editores siguen trabajando igual y Next.js consume el contenido por la API REST o GraphQL. Con un webhook al publicar, la revalidación bajo demanda actualiza solo la página que cambió.

Ese esquema también resuelve un riesgo silencioso: los enlaces internos dentro del contenido. Las entradas viejas están llenas de enlaces absolutos al dominio y a rutas de WordPress. Hay que reescribirlos al migrar, o cada uno pasará por una redirección.

Optimización de rendimiento en entornos enterprise

El rendimiento forma parte del SEO a través de Core Web Vitals. Una migración que conserva URLs y metadatos pero empeora la experiencia también pierde terreno.

Next.js da herramientas para cada métrica, pero hay que usarlas a propósito:

  • LCP (carga del elemento principal). La imagen principal usa next/image con prioridad, y el HTML llega desde caché. El servidor no debería consultar el CMS en cada visita.
  • CLS (estabilidad visual). Imágenes con dimensiones declaradas y fuentes con next/font, que se autoalojan y evitan el salto al cargar.
  • INP (respuesta a la interacción). Menos JavaScript en el cliente. Cada componente marcado con "use client" suma al paquete; úsalo solo donde hay interacción real.

Los scripts de terceros merecen una revisión aparte. Etiquetas de analítica, chats y píxeles suelen pesar más que el sitio. El componente Script permite diferirlos para que no compitan con el contenido.

Mide con datos de campo, no solo con Lighthouse en tu laptop. Antes de migrar, guarda las métricas reales del sitio en WordPress. Son la línea base contra la que se compara el sitio nuevo.

Verificación antes de cambiar el DNS

La parte que más se salta es la que más protege. Antes del corte, el sitio nuevo se compara con el viejo URL por URL, usando el HTML crudo, sin ejecutar JavaScript.

Este script recorre el inventario contra el staging y reporta cualquier diferencia en estado, título o canónica:

# ⚠️ Antes del corte: respalda la base de WordPress y la configuración del DNS.
STAGING="https://staging.ejemplo.com"
while read -r url; do
  ruta="${url#https://www.ejemplo.com}"
  # -L sigue la redirección: lo que importa es dónde termina y con qué código.
  final=$(curl -s -o nuevo.html -L -w '%{http_code} %{num_redirects}' "$STAGING$ruta")
  titulo=$(grep -o '<title>[^<]*' nuevo.html | sed 's/<title>//')
  canonica=$(grep -o 'rel="canonical" href="[^"]*' nuevo.html | sed 's/.*href="//')
  echo -e "$ruta\t$final\t$titulo\t$canonica"
done < urls-actuales.txt > comparacion.tsv

El resultado es una tabla que se revisa con criterios claros: ninguna URL en 404, ninguna con más de una redirección, ningún título vacío o repetido, y cada canónica apuntando a la URL final en producción.

El corte se hace cuando la tabla está limpia, no cuando el diseño está aprobado. Después del cambio de DNS, la misma revisión corre contra producción, y Search Console se vigila de cerca las semanas siguientes.

Conserva el WordPress anterior accesible de forma privada durante un tiempo. Si aparece una URL que nadie inventarió, necesitas poder consultar qué había ahí.

Lo que NO debes hacer

  • Redirigir todo a la portada. Es la salida rápida cuando el inventario está incompleto. Los buscadores lo tratan como contenido eliminado y el posicionamiento de cada página se pierde.
  • Construir el sitio nuevo como una SPA que pinta todo en el cliente. En tu navegador se ve igual. En el HTML que recibe un rastreador falta el contenido, y los títulos son todos iguales.
  • Cambiar URLs, diseño, contenido y dominio en el mismo corte. Si el tráfico cae, no hay forma de saber qué lo provocó. Migra la plataforma primero; los cambios editoriales, después.

El siguiente paso

Una migración de WordPress a React con Next.js puede ser neutral para el SEO, o incluso mejorarlo por rendimiento. La diferencia la hace el método: inventario completo, un mapa de redirecciones revisado, metadatos en el servidor y una verificación que se hace antes del corte, no después.

Si tu equipo está por migrar un sitio grande a React y no puede darse el lujo de perder posicionamiento, o necesita auditar una migración que ya salió con problemas, en Conqr Digital Agency diseñamos arquitecturas que escalan. Revisemos tu caso.

Si quieres conocer cómo trabajo proyectos de este tipo, revisa el servicio de desarrollo web a medida.

Preguntas frecuentes

¿Migrar de WordPress a React afecta el SEO?
Puede afectarlo mucho si cambian las URLs sin redirecciones, si el contenido se pinta solo en el navegador o si se pierden títulos y canónicas. Con un mapa de 301 completo, renderizado en el servidor y metadatos equivalentes, la migración puede ser neutral para el posicionamiento.
¿Uso redirects de next.config o middleware?
Para un mapa fijo de URLs viejas a nuevas, redirects en next.config: es declarativo y se resuelve antes de renderizar. El middleware conviene cuando la regla depende de la petición, por ejemplo patrones con lógica o mapas que viven fuera del código.
¿Puedo seguir usando WordPress como CMS?
Sí. Es un esquema común: WordPress queda como backend editorial y Next.js consume su contenido por la API REST o GraphQL. Los editores no cambian su flujo y el frontend gana control sobre rendimiento y metadatos.
Samuel Torres Pichardo

Samuel Torres Pichardo
Ingeniero de software y cloud en Ciudad de México, con unos 3 años de experiencia profesional.
LinkedIn · GitHub · Contacto

Contacto profesional:

Ciudad de México, México

Disponible para proyectos remotos y presenciales.
Ingeniero en Tecnologías de Computación y Telecomunicaciones

© 2026 Ing. Samuel Torres. Ingeniería aplicada.