Te despiertas con una alerta de Search Console: “Duplicate, submitted URL not selected as canonical.”
El tráfico ha caído. Tu equipo de contenidos jura que no cambió nada. Tu agencia SEO te envía una hoja de cálculo llena de URLs casi idénticas que solo difieren por /en/ frente a ningún prefijo, o un misterioso ?lang=en.
Esta es la trampa de Polylang: el sitio “funciona” para humanos, pero la superficie de URLs se expande silenciosamente. Los rastreadores no perdonan la ambigüedad.
Y las cachés disfrutan empeorarlo.
Qué significa realmente “páginas duplicadas” en el mundo de Polylang
“Páginas duplicadas” es un término sobrecargado. En una configuración con Polylang puede significar al menos cuatro cosas distintas, y cada una exige una solución diferente.
1) Contenido duplicado por múltiples URLs que resuelven a la misma página de idioma
Ejemplo: /about/ y /en/about/ sirven ambos en inglés. O /de/uber-uns/ y /uber-uns/?lang=de.
Un humano hace clic en una. Google indexa ambas y escoge una canónica, a veces la equivocada.
2) “Entidades” duplicadas en WordPress: posts creados dos veces
Este es el escenario de “tenemos dos páginas en inglés tituladas About”. Normalmente lo causan importadores, maquetadores de páginas o un flujo de traducción que creó un post nuevo en lugar de vincular una traducción.
Esto es más desordenado porque no es solo higiene de URLs; es integridad de datos.
3) Archivos de taxonomía y páginas de términos duplicadas
Categorías y etiquetas pueden multiplicarse. Un slug de categoría traducido podría existir tanto traducido como sin traducir. Peor: el mismo ID de término puede exponerse bajo múltiples contextos de idioma por una filtración de idioma mal configurada.
4) Objetos de caché duplicados que sirven el idioma equivocado bajo la URL correcta
Este es el asesino silencioso: /fr/produit/ a veces devuelve contenido en inglés porque la clave de caché ignoró el idioma. Entonces Polylang intenta “arreglarlo” con redirecciones.
Resultado: bucles de redirección, canonicals mezclados y una fiesta de rastreadores a la que no invitaste.
La pregunta correcta no es “¿tenemos duplicados?” sino “¿qué capa está duplicando: enrutamiento, canonicalización, datos o caché?”
Diagnostica eso primero. Arreglar la capa equivocada es como acabar con un sitio bilingüe y además roto en dos direcciones.
Cómo Polylang crea duplicados (mecanismos habituales)
Idioma en la URL: directorio, subdominio o parámetro
Polylang soporta negociación de idioma vía directorio en la URL (por ejemplo, /en/), subdominio (por ejemplo, en.example.com) o parámetro (por ejemplo, ?lang=en).
Cada uno tiene distintos modos de fallo.
- Basado en directorios es generalmente lo menos malo para SEO, pero exige redirecciones estrictas para que exista una sola forma.
- Basado en subdominios complica el alcance de cookies y la caché, pero aísla los idiomas limpiamente si se hace bien.
- Basado en parámetros es la forma más fácil de fabricar duplicados, porque muchos sistemas tratan las query strings como opcionales “misma página”. Los rastreadores no lo hacen.
La ambigüedad del “idioma por defecto”
La mayoría de incidentes con duplicados en Polylang empiezan con el idioma por defecto siendo accesible de dos maneras:
/about/ y /en/about/.
Alguien decide “ambas están bien”. No lo están.
Elige una. Redirige la otra. Y luego hazlo cumplir en el borde (Nginx/CDN), no solo en PHP donde es más lento y fácil de eludir.
Canonicals y hreflang que se desalinean
Las etiquetas canonical indican a los rastreadores cuál URL es la preferida. hreflang les indica cómo se relacionan las variantes de idioma/región.
Cuando discrepan —por ejemplo, la canonical apunta a /about/ pero hreflang lista /en/about/— les estás contando dos historias diferentes.
Google escogerá una tercera historia.
Sitemaps que listan ambas formas
Si tu sitemap emite tanto /en/about/ como /about/ (o mezcla variantes con parámetros), has escalado el problema de “posible duplicado” a “duplicado invitado”.
Los sitemaps son una declaración de intención. Si listás basura, obtendrás indexación basura.
Caché que ignora el idioma
Las cachés necesitan una clave. Si el idioma se decide por cookie, header o query param y no variás la clave de caché en consecuencia, servirás el idioma equivocado.
Polylang entonces puede redirigir según el idioma detectado, provocando bucles y rutas de rastreo duplicadas.
Broma #1: Las cachés son como bebés: si no pones reglas claras, con total confianza te darán lo incorrecto.
Hechos y contexto que cambian cómo depuras
- El núcleo de WordPress no fue construido pensando primero en multilingües. Existe internacionalización, pero el enrutamiento de contenido multilengua es territorio de plugins, lo que significa que la “fuente de la verdad” está fragmentada.
- Las etiquetas canonical se convirtieron en una herramienta SEO estándar en 2009. Mucho comportamiento SEO de WordPress asume una canonical única por objeto de contenido; lo multilingüe introduce “canonical por variante”.
- hreflang no es un impulso de posicionamiento; es una pista de desambiguación. Si lo haces mal, no solo pierdes la oportunidad: creas confusión sobre qué URL pertenece a qué índice.
- Los motores de búsqueda tratan los parámetros de consulta como URLs separadas a menos que se demuestre lo contrario. La negociación de idioma por parámetros es básicamente duplicación con mejor interfaz.
- Las cachés HTTP típicamente ignoran cookies por defecto. Si la selección de idioma se almacena en una cookie, tu caché debe variar explícitamente por ella —o debes evitar la selección por cookie para páginas cacheadas.
- Los CDNs pueden normalizar URLs de maneras sorprendentes. Algunas configuraciones eliminan o reordenan parámetros de consulta, lo que puede fusionar idiomas en el mismo objeto de caché.
- Los robots y prefetchers no se comportan como los navegadores. Podrían no aceptar cookies, no ejecutar JS, y rastrear enlaces de idiomas alternativos a gran escala.
- Polylang almacena las relaciones de idioma en sus propias tablas/meta. Si migras, importas o copias posts sin preservar ese mapeo, las traducciones quedan huérfanas, y las huérfanas se duplican durante los “arreglos”.
- Los cambios de permalink son migraciones de URL. Pasar de
?lang=a/en/no es “una configuración”. Es un plan completo de redirecciones, un plan de purga de caché y un plan de reindexación.
Un principio de fiabilidad aplica aquí. Una idea parafraseada atribuida a John Allspaw: los incidentes surgen de trabajos normales que interactúan de maneras inesperadas, no por una sola persona mala
.
La duplicación multilingüe es exactamente eso: comportamiento normal de plugins + caché normal + herramientas SEO normales = un lío emergente raro.
Guía de diagnóstico rápido
Cuando alguien dice “Polylang está creando páginas duplicadas”, generalmente describen un síntoma visto en analítica o herramientas SEO.
Tu trabajo es localizar rápidamente la capa que duplica.
Primero: determina si los duplicados son a nivel de URL o de contenido
- ¿Varias URLs devuelven el mismo HTML (mismo idioma, mismo contenido)? Eso es duplicación a nivel de URL (redirección/canonical/sitemap/caché).
- ¿Existen múltiples posts/páginas en WordPress con el mismo idioma y contenido similar? Eso es duplicación a nivel de contenido (datos/flujo/importación).
Segundo: verifica la coherencia de canonical + hreflang en una página afectada
- La canonical debe apuntar a la forma de URL preferida para ese idioma.
- El conjunto de hreflang debe ser completo, consistente y autorreferencial (cada idioma apunta correctamente a sí mismo).
Tercero: comprueba la variación de caché
- Si el idioma cambia según cookie/header/query param, asegura que las claves de caché varíen en consecuencia.
- Verifica si el CDN está cacheando HTML para usuarios desconectados y si distingue por idioma.
Cuarto: audita las redirecciones para el idioma por defecto
- Elige un esquema de URL canónico único para el idioma por defecto y aplícalo con 301s.
- Elimina “dos puertas” hacia el mismo contenido. Los rastreadores usarán ambas puertas.
Tareas prácticas: comandos, salidas, decisiones
Estos son chequeos prácticos que puedes ejecutar desde una shell en un nodo web o un bastión con acceso.
Cada tarea incluye (1) un comando, (2) qué significa la salida y (3) la decisión que tomas.
Ajusta dominios y rutas a tu entorno.
Task 1: Confirmar si dos URLs devuelven contenido idéntico
cr0x@server:~$ curl -sS -D- https://example.com/about/ -o /tmp/a.html | sed -n '1,20p'
HTTP/2 200
content-type: text/html; charset=UTF-8
cache-control: public, max-age=600
...
cr0x@server:~$ curl -sS https://example.com/en/about/ -o /tmp/b.html && sha256sum /tmp/a.html /tmp/b.html
e3b0c44298fc1c149afbf4c8996fb924... /tmp/a.html
e3b0c44298fc1c149afbf4c8996fb924... /tmp/b.html
Significado: Hashes idénticos sugieren fuertemente que el mismo HTML se sirve en ambas URLs. Eso es duplicación de URL, no duplicación editorial.
Decisión: Elige una forma de URL y redirige la otra con un 301; luego alinea canonical y sitemap con la ganadora.
Task 2: Inspeccionar canonical y hreflang en la página
cr0x@server:~$ curl -sS https://example.com/en/about/ | grep -Eo '<link[^>]+(canonical|alternate)[^>]+' | head
<link rel="canonical" href="https://example.com/about/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos/" />
Significado: La canonical apunta a /about/ mientras que hreflang se autorreferencia en /en/about/. Esa discrepancia es un clásico problema de indexación.
Decisión: Haz que la canonical sea consistente con tu esquema elegido (probablemente /en/about/ si prefijas todos los idiomas, o /about/ si el idioma por defecto no tiene prefijo y solo existe una ruta).
Task 3: Seguir redirecciones y ver si se aplica la imposición de idioma
cr0x@server:~$ curl -sS -I -L https://example.com/about/ | sed -n '1,40p'
HTTP/2 200
content-type: text/html; charset=UTF-8
...
Significado: Sin redirecciones. Si /about/ y /en/about/ devuelven 200, tienes dos URLs indexables.
Decisión: Añade una redirección 301 para una de las formas, idealmente a nivel de Nginx/CDN.
Task 4: Comprobar si las cookies cambian el idioma y por lo tanto deben variar la caché
cr0x@server:~$ curl -sS -I https://example.com/about/ | grep -i 'set-cookie'
set-cookie: pll_language=en; path=/; secure; HttpOnly; SameSite=Lax
Significado: Polylang está estableciendo una cookie de idioma. Si tu caché no varía por esa cookie, los usuarios pueden ver el idioma equivocado.
Decisión: O (a) evita el cambio de idioma impulsado por cookies para páginas cacheadas, imponiendo el idioma en la URL, o (b) configura la variación de caché correctamente (a menudo doloroso y caro).
Task 5: Verificar si la caché varía por cookie/header (pistas en la respuesta)
cr0x@server:~$ curl -sS -I https://example.com/en/about/ | grep -iE 'vary|x-cache|cf-cache-status|age'
vary: Accept-Encoding
x-cache: HIT
age: 531
Significado: Vary no menciona cookie ni un header de idioma, y la caché está en HIT. Si la selección de idioma depende de cookie, esto es sospechoso.
Decisión: Arregla la clave de caché (reglas en el borde) o re-arquitecta la negociación de idioma para que sea basada en URL para tráfico anónimo.
Task 6: Comparar marcadores de idioma en el HTML entre variantes
cr0x@server:~$ curl -sS https://example.com/fr/a-propos/ | grep -Eo '<html[^>]+' | head -n 1
<html lang="en-US">
Significado: URL en francés devolviendo lang="en-US" sugiere fuertemente contenido en idioma equivocado o una mala configuración del tema.
Decisión: Trátalo como un sangrado de caché o un bug de plantilla; verifica el contexto de idioma de Polylang y las reglas de caché antes de tocar ajustes SEO.
Task 7: Comprobar si el sitemap lista duplicados
cr0x@server:~$ curl -sS https://example.com/sitemap.xml | grep -Eo '<loc>[^<]+' | sed 's/<loc>//' | head
https://example.com/about/
https://example.com/en/about/
https://example.com/fr/a-propos/
Significado: El sitemap incluye explícitamente tanto la URL inglesa por defecto como la prefijada.
Decisión: Corrige la generación del sitemap (plugin SEO + integración Polylang) para que solo se listén las URLs canónicas.
Task 8: Buscar en logs de acceso tormentas de rastreo con el parámetro de idioma
cr0x@server:~$ sudo awk '$7 ~ /lang=/ {count++} END {print count}' /var/log/nginx/access.log
18427
Significado: Muchas solicitudes incluyen lang=. O bien los enlaces internos están filtrando URLs con parámetros, o los bots las descubrieron.
Decisión: Deja de generar URLs con parámetros, redirígelas con 301 a los equivalentes en directorio/subdominio, y elimínalas de sitemaps y enlaces internos.
Task 9: Confirmar si diferentes query strings se cachean como el mismo objeto
cr0x@server:~$ curl -sS -I "https://example.com/about/?lang=en" | grep -iE 'x-cache|cf-cache-status|age'
x-cache: HIT
age: 590
cr0x@server:~$ curl -sS -I "https://example.com/about/?lang=fr" | grep -iE 'x-cache|cf-cache-status|age'
x-cache: HIT
age: 590
Significado: Mismo age y patrón HIT sugiere que la caché podría estar ignorando query strings o normalizándolas.
Decisión: Arregla la clave de caché del CDN/Nginx para incluir la query string cuando corresponda, o (mejor) elimina totalmente el modo por parámetros de idioma.
Task 10: Validar que WordPress vea la URL de inicio correcta por petición
cr0x@server:~$ wp option get home
https://example.com
cr0x@server:~$ wp option get siteurl
https://example.com
Significado: La línea base parece normal. Esta comprobación importa porque un home/siteurl desajustado puede producir canonicals y redirecciones mixtas que parecen “multilingües”.
Decisión: Si difieren o son incorrectos (http vs https), arréglalos antes de culpar a Polylang.
Task 11: Inspeccionar rápidamente la configuración de Polylang
cr0x@server:~$ wp plugin list --status=active | grep -i polylang
polylang 3.6.2 active
cr0x@server:~$ wp option get polylang
Error: Could not get 'polylang' option. Does it exist?
Significado: Polylang almacena muchas cosas en sus propias tablas y múltiples opciones; no necesariamente encontrarás un único blob de opción ordenado.
Decisión: Usa inspección de base de datos para las tablas de Polylang y verifica el modo de URL en la interfaz de administración; no asumas que la CLI te cuenta todo.
Task 12: Identificar posts duplicados por título dentro de un idioma (duplicación a nivel contenido)
cr0x@server:~$ wp db query "SELECT p.ID, p.post_title, pm.meta_value AS lang
FROM wp_posts p
JOIN wp_term_relationships tr ON tr.object_id = p.ID
JOIN wp_term_taxonomy tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
JOIN wp_terms t ON t.term_id = tt.term_id
LEFT JOIN wp_postmeta pm ON pm.post_id = p.ID AND pm.meta_key = '_pll_post_language'
WHERE p.post_type='page' AND p.post_status='publish' AND tt.taxonomy='language'
ORDER BY p.post_title LIMIT 10;"
+-----+----------------+------+
| ID | post_title | lang |
+-----+----------------+------+
| 311 | About | NULL |
| 947 | About | NULL |
| 102 | Careers | NULL |
+-----+----------------+------+
Significado: Esta salida es ilustrativa: el enlace de idioma no siempre está en _pll_post_language como con otros plugins; puede aparecer NULL según la versión del esquema.
La parte útil es la técnica: consulta por duplicados y luego verifica sus relaciones de término de idioma en Polylang.
Decisión: Si realmente tienes objetos de post duplicados, corrige el mapeo de traducciones (vincula las traducciones) o elimina/redirige los duplicados no deseados. No soluciones un problema de datos solo con etiquetas canonical.
Task 13: Revisar Nginx por reglas de reescritura que creen URLs sombra
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -RIn "rewrite.*lang|return 30[12].*/en/|try_files.*\\$args" /etc/nginx | head
/etc/nginx/sites-enabled/example.conf:47: rewrite ^/about/$ /en/about/ permanent;
/etc/nginx/sites-enabled/example.conf:63: try_files $uri $uri/ /index.php?$args;
Significado: Puede que tengas redirecciones hechas a mano. El try_files con $args preserva query strings, lo que puede mantener vivas las URLs con parámetros de idioma.
Decisión: Haz las redirecciones consistentes y agresivas: si el modo por parámetro no se desea, quítalo/redirígelo. Asegúrate también de no reescribir solo algunas rutas y dejar otras duplicadas.
Task 14: Confirmar que la base de datos no contiene ambas variantes de slug para el mismo idioma
cr0x@server:~$ wp db query "SELECT post_name, COUNT(*) c FROM wp_posts WHERE post_type='page' AND post_status='publish' GROUP BY post_name HAVING c > 1 ORDER BY c DESC LIMIT 10;"
+-----------+---+
| post_name | c |
+-----------+---+
| about | 2 |
+-----------+---+
Significado: Dos páginas publicadas comparten el mismo slug. WordPress desambiguará con sufijos o rarezas de enrutamiento dependiendo de la jerarquía, pero el enrutamiento multilingüe puede hacer que esto parezca “páginas duplicadas por idioma”.
Decisión: Arregla el modelo de contenido: slugs únicos por contexto de idioma y asegura que las traducciones estén vinculadas en lugar de duplicadas.
Task 15: Comprobar si se sirve salida con idioma mezclado desde la caché (revisión puntual)
cr0x@server:~$ for u in /en/about/ /fr/a-propos/; do echo "== $u"; curl -sS https://example.com$u | grep -Eo '<title>[^<]+' | head -n1; done
== /en/about/
<title>About - Example</title>
== /fr/a-propos/
<title>About - Example</title>
Significado: La URL francesa devuelve título en inglés. Esto casi nunca es “SEO”. Es variación de caché, mapeo de traducciones roto o detección de idioma que cae al valor por defecto.
Decisión: Deshabilita la caché de página completa temporalmente para confirmar, luego corrige la clave de caché y purga.
Task 16: Verificar encabezados para corrección de host/protocolo en canonical
cr0x@server:~$ curl -sS https://example.com/en/about/ | grep -Eo '<link rel="canonical" href="[^"]+' | head -n1
<link rel="canonical" href="http://example.com/en/about/
Significado: La canonical apunta a HTTP mientras el sitio es HTTPS. Eso crea “duplicados” por esquema, y lo multilingüe solo agranda el grafo.
Decisión: Arregla los ajustes de URL en WordPress, los headers del proxy inverso (X-Forwarded-Proto) y la configuración del plugin SEO para que las canonicals usen el esquema público correcto.
Modos de fallo por capa (WordPress, plugins, web, CDN, bots)
Capa WordPress: permalinks y páginas jerárquicas
El enrutamiento de WordPress es determinista hasta que introduces múltiples URLs “válidas” para el mismo contenido. Entonces entras en territorio de ambigüedad:
páginas jerárquicas, attachments y reglas de reescritura auto-generadas pueden dar a los rastreadores múltiples rutas.
Si tu idioma por defecto no tiene prefijo, el permalink del idioma por defecto debe ser la única variante accesible. Si es accesible tanto con prefijo como sin él, has creado una segunda identidad.
WordPress no te lo impedirá. WordPress es así de educado.
Capa Polylang: negociación de idioma y mapeo de traducciones
Polylang hace bien lo que hace: asigna contexto de idioma, construye enlaces alternativos y te permite traducir contenido.
La trampa es asumir que también gobierna la caché, las redirecciones en el borde y el comportamiento de sitemap de otros plugins. No lo hace.
El mapeo de traducciones importa. Si una página existe en dos idiomas pero no están vinculadas como traducciones, Polylang puede tratarlas como páginas independientes.
Entonces un editor con buena intención duplica una página “para traducirla”, y ahora tienes dos páginas en el mismo idioma porque alguien hizo clic en la opción equivocada en un desplegable.
Capa plugin SEO: canonicals, sitemaps y directivas robots
La mayoría de plugins SEO tienen integración multilingüe, pero no es mágica. Cuando las integraciones fallan, fallan silenciosamente.
Terminas con:
- Sitemaps que listan URLs canónicas prefijadas y sin prefijo del idioma por defecto.
- Canonicals que ignoran el contexto de idioma actual.
- Enlaces alternativos correctos pero que apuntan a URLs no canónicas.
Capa servidor web: redirecciones, normalización y preservación de query-string
Las configuraciones de Nginx/Apache a menudo preservan query strings por defecto. Eso normalmente es correcto.
En setups multilingües, puede mantener modos de idioma “muertos” vivos para siempre: ?lang= continúa resolviendo y se indexa.
Las reglas de normalización también pueden crear duplicados: barra final vs sin barra, mayúsculas vs minúsculas, www vs dominio raíz. Multiplica eso por 5 idiomas y has construido una granja de URLs.
Capa CDN: claves de caché y normalización
Los CDNs reducen carga y mejoran rendimiento. También hacen que los bugs sean globales en unos 45 segundos.
Si el CDN cachea HTML y el idioma varía por cookie o header, debes configurar correctamente la clave de caché.
Si no puedes, no cachees HTML que varíe por cookie. Cachea solo assets estáticos, o pasa a enrutamiento de idioma basado en URL.
Capa bots: cómo los rastreadores descubren los duplicados
Los duplicados suelen hacerse visibles porque:
- Enlaces internos exponen ambas variantes (menús, selectores de idioma, migas, errores en etiquetas canonical).
- Los sitemaps las listan.
- Cadenas de redirección las exponen.
- Enlaces externos incluyen la forma “incorrecta”, y tu sitio la acepta sin redirigir.
No pierdas tiempo culpando a “Google por ser tonto”. Si permites dos URLs, los rastreadores usarán dos URLs. Eso no es un bug; es su trabajo.
Tres microhistorias corporativas desde las trincheras multilingües
Microhistoria 1: El incidente causado por una suposición errónea
Una empresa B2B mediana lanzó un sitio bilingüe: inglés por defecto y se añadió francés para un nuevo mercado.
Usaron Polylang con directorios de idioma y mantuvieron el idioma por defecto accesible tanto como / como /en/ “porque marketing quería el aspecto /en/”.
La suposición: las etiquetas canonical lo harían bien. ¿El plugin SEO elegiría una, verdad?
Lo hizo—a veces. Para algunas plantillas emitía canonical sin /en/. Para otras la incluía. La navegación del header enlazaba a /en/, el footer enlazaba sin prefijo.
Así los rastreadores vieron dos grafos de enlaces internos con igual autoridad.
El resultado no fue un fuego inmediato. Fue podrido lento: más presupuesto de rastreo gastado, oscilación de índice y contenido mostrando en el idioma equivocado para búsquedas de marca.
Luego el equipo de soporte notó algo embarazoso: clientes en Francia aterrizaban en páginas en inglés porque la “preferida” en el índice era la inglesa sin directorio, y Google la consideró la principal.
La solución fue aburrida: elige un esquema de URL, 301 para la otra, regenera sitemaps y purga cachés. Las posiciones se estabilizaron en unas semanas.
La lección real: nunca permitas dos URLs “válidas” para la misma variante de idioma. Las canonicals no son excusa para la indecisión.
Microhistoria 2: La optimización que salió mal
Un equipo de ecommerce tenía problemas de rendimiento durante campañas. Alguien activó caché de página completa en el CDN para todo el tráfico anónimo.
Buenos gráficos. La carga bajó. La velocidad mejoró. Todos celebraron.
Dos días después, servicio al cliente reporta: “Las páginas en español muestran inglés al azar”. No era aleatorio. La clave del CDN ignoraba la cookie de Polylang y no variaba por Accept-Language.
La primera petición a una URL ganó; todos los demás obtuvieron ese idioma cacheado.
Polylang intentó corregir el idioma basado en cookie y redirigió a algunos usuarios. Esas redirecciones también fueron cacheadas incorrectamente.
El sitio desarrolló una nueva característica: bucles de redirección que solo ocurrían en una región, detrás de un ISP, con un conjunto de cookies. Clásico.
El postmortem fue directo. La optimización era válida en un mundo monolingüe y destructiva en uno multilingüe.
Revirtieron el cacheo de HTML, mantuvieron el cacheo de assets estáticos y luego reintrodujeron cacheo de HTML solo después de mover la selección de idioma completamente a la URL y variar las claves de caché correctamente.
Microhistoria 3: La práctica aburrida pero correcta que salvó el día
Una organización mediática ejecutaba Polylang en seis idiomas. Ya habían sufrido antes, así que impusieron una regla:
cada variante de idioma debe tener exactamente una URL canónica, y cada variante no canónica debe 301 en un solo salto.
También tenían un trabajo programado que muestreaba unas pocas docenas de URLs por idioma, comprobaba coherencia de canonical y hreflang, y alertaba si:
la canonical no coincidía con la ruta de idioma de la petición, o si un destino hreflang devolvía una cadena de redirección.
Un viernes, una actualización de tema cambió cómo el selector de idioma generaba enlaces. Empezó a emitir URLs basadas en parámetros (?lang=) para algunas plantillas.
El monitoreo lo detectó en la hora: aparición repentina de lang= en logs y un pico en respuestas 200 no canónicas.
Revirtieron el cambio de tema, añadieron una redirección defensiva de ?lang= a la forma por directorio, purgaron cachés y siguieron.
Sin drama, sin cliff SEO. Solo una pequeña perturbación. El ingrediente secreto no fue genialidad; fue tener una opinión y aplicarla continuamente.
Broma #2: La mejor estrategia multilingüe es como una buena rotación on-call—poco emocionante, documentada y nadie habla de ella en las fiestas.
Errores comunes: síntomas → causa raíz → solución
1) Síntoma: /en/ y no-/en/ ambos indexan para inglés
Causa raíz: El idioma por defecto es accesible por dos formas de URL; no hay redirecciones estrictas; el sitemap incluye ambas.
Solución: Elige un esquema canónico para el idioma por defecto. Añade redirecciones 301 para la otra. Asegura que las etiquetas canonical y sitemaps solo emitan la forma canónica.
2) Síntoma: La URL en francés a veces muestra contenido en inglés
Causa raíz: La clave de caché carece de dimensión de idioma (cookie/header/query); CDN o fastcgi de Nginx sirve la variante equivocada.
Solución: Mueve la negociación de idioma a directorios/subdominios para usuarios anónimos; o varía la caché por cookie/header de idioma y confirma con tests repetidos de curl. Purga cachés.
3) Síntoma: Search Console dice “Alternate page with proper canonical tag” para miles de URLs
Causa raíz: URLs basadas en parámetros (?lang=) o variantes de barra final son rastreables; las canonicals apuntan a otro lado pero las páginas aún responden 200.
Solución: 301 las variantes con parámetros a las URLs canónicas por directorio; normaliza barras finales; elimina duplicados del sitemap; asegura que los enlaces internos no emitan parámetros.
4) Síntoma: advertencias hreflang (sin etiquetas de retorno, códigos erróneos)
Causa raíz: Mapeo de traducciones incompleto, idiomas eliminados sin limpieza, o fallo de integración del plugin SEO en algunas plantillas.
Solución: Asegura que cada variante enumere un conjunto completo de hreflang incluido el propio; arregla enlaces de traducción; verifica códigos (p. ej., en, fr) y variantes regionales si se usan.
5) Síntoma: páginas de archivo de categoría/etiqueta duplicadas por idioma
Causa raíz: Misconfiguración de traducción de taxonomías; slugs de término duplicados; archivos no filtrados por idioma de forma consistente.
Solución: Decide si los slugs de taxonomía se traducen; aplica un enfoque y hazlo cumplir; asegura que las consultas de archivo filtren por idioma; redirige archivos no deseados.
6) Síntoma: Tras migrar dominios, las URLs antiguas de idioma siguen resolviendo
Causa raíz: Reglas de redirección demasiado genéricas; cachés mantienen HTML antiguo; canonicals mixtos (http/https, www/apex) mantienen variantes antiguas vivas.
Solución: Implementa redirecciones explícitas de host/esquema; purga CDN; verifica canonicals en páginas representativas; revisa encabezados de respuesta para host y esquema correctos.
7) Síntoma: Los editores ven múltiples páginas “About” en el mismo idioma
Causa raíz: El flujo de traducción creó posts nuevos sin vincular las traducciones; importaciones duplicaron contenido; plantillas de maquetador clonaron objetos de contenido.
Solución: Desduplicar contenido en WordPress: vincula traducciones correctamente, elimina/combina extras e implementa guardrails editoriales (roles, flujo, formación).
8) Síntoma: Bucles de redirección al cambiar de idioma
Causa raíz: Redirecciones en conflicto (CDN + Nginx + Polylang), caché sirviendo idioma equivocado que dispara redirección, o reglas de barra final que chocan con prefijos de idioma.
Solución: Mapea la lógica de redirección de forma central (preferir el borde), reduce capas de redirección, prueba con curl -I -L y asegura que la caché sirva contenido correcto por URL.
Listas de verificación / plan paso a paso
Paso a paso: elige una única verdad de URL y aplícala
- Elige una estrategia de URL: directorios o subdominios. Para la mayoría de sitios: directorios.
- Decide comportamiento del idioma por defecto: con prefijo o sin prefijo. Si sin prefijo, asegura que el prefijo del idioma por defecto redirija. Si con prefijo, asegura que el sin prefijo redirija hacia él.
- Normaliza esquema y host: un solo host HTTPS; redirige el resto.
- Elimina URLs con parámetros de idioma: 301 a sus equivalentes canónicos por directorio/subdominio.
- Haz que las canonicals coincidan: la canonical debe corresponder al esquema de URL elegido para esa variante de idioma.
- Haz coherente el hreflang: conjunto completo, códigos correctos, autorreferencial y los destinos deben devolver 200 (no redirigir).
- Corrige la emisión del sitemap: solo URLs canónicas, alternates correctos, sin variantes con parámetros.
- Audita enlaces internos: menús, pies, migas, posts relacionados, selector de idioma—sin formas mezcladas.
- Arregla la caché: asegura que la caché varíe por idioma, o cachea solo contenido invariable por idioma. Para HTML anónimo, mejor idioma en la URL.
- P urga con agresividad: CDN + caché servidor + caché de plugin. Luego valida con muestreos de curl.
- Monitorea: registra consultas de
?lang=, rastrea respuestas 200 en formas no canónicas, vigila deriva de canonical/hreflang después de despliegues.
Checklist de lanzamiento (la que realmente sigues)
- Para 5 páginas representativas por idioma: confirma un único salto al canonical y etiqueta canonical correcta.
- Confirma que el atributo
langen HTML coincida con la URL de idioma. - Confirma que los enlaces del selector de idioma no usen query params.
- Confirma que el sitemap contiene solo formas de URL canónicas.
- Confirma que los encabezados de caché son sensatos y varían apropiadamente (o que el cacheo de HTML está deshabilitado si no puede variarse correctamente).
- Busca en logs picos repentinos en formas no canónicas (
/en/,?lang=, barras finales mezcladas).
Checklist de higiene de datos (previene duplicados editoriales)
- Define el flujo de “creación de traducción”: siempre crear traducciones mediante la UI de enlace de Polylang, no copiando/pegando páginas nuevas.
- Restringe quién puede publicar en idiomas secundarios hasta que el proceso esté estable.
- Corre informes periódicos de títulos/slugs duplicados y revísalos con operaciones de contenido.
- Antes de importaciones: prueba en staging y verifica que el mapeo de traducciones sobreviva la migración.
Preguntas frecuentes
1) ¿Polylang es “malo para SEO”?
No. Polylang está bien. La trampa es permitir que múltiples formas de URL resuelvan al mismo contenido y asumir que las canonicals lo corregirán.
SEO odia la ambigüedad más que cualquier plugin en particular.
2) ¿El idioma por defecto debe llevar prefijo (/en/) o no?
Cualquiera puede funcionar. Elige una y aplícala estrictamente.
Si quieres máxima consistencia y menos casos extremos, prefija todo, incluido el idioma por defecto. Si prefieres URLs por defecto más limpias, deja el idioma por defecto sin prefijo—pero asegura que /en/ redirija en todas partes.
3) ¿Por qué veo URLs ?lang= aunque uso directorios de idioma?
Normalmente un componente del tema, selector de idioma o plugin está generando enlaces con parámetros.
A veces es un comportamiento de fallback cuando Polylang no puede resolver una traducción. Trátalo como un bug: las URLs con parámetros deberían 301 a la forma por directorio.
4) ¿Puedo simplemente añadir noindex a los duplicados?
Puedes, pero raramente es la mejor primera solución. Si los duplicados son accesibles y están enlazados internamente, los rastreadores seguirán gastando tiempo en ellos.
Prefiere redirecciones 301 a una única URL canónica. Usa noindex sólo cuando las redirecciones no sean viables (raro) o para casos especiales como listados filtrados.
5) Mi CDN cachea HTML. ¿Cómo evito páginas con idioma mezclado?
Haz que la selección de idioma forme parte de la URL (/fr/, /en/) y configura la clave de caché para incluir la ruta completa.
Evita la negociación por cookie para HTML cacheable anónimo. Si debes usar cookies, varía la caché por esa cookie explícitamente y pruébalo.
6) ¿Por qué Search Console muestra duplicados después de arreglar redirecciones?
La indexación no es instantánea. Además, puede que aún estés emitiendo duplicados vía sitemap, enlaces internos o canonicals.
Confirma que la URL no canónica ahora devuelve un 301 y que la página canónica tenga su canonical apuntando a sí misma, no a otra variante.
7) ¿Y las taxonomías traducidas—deben traducirse los slugs de categoría?
Decide según audiencia y escala. Los slugs traducidos pueden ofrecer mejor UX, pero aumentan la complejidad.
Si traduces slugs de taxonomía, asegúrate de no exponer archivos no traducidos para el mismo idioma. Una lengua, una URL de archivo de término.
8) ¿Cambiar la estructura de permalinks causa páginas duplicadas?
Puede. Cambiar permalinks cambia identidades de URL. En setups multilingües, multiplica el radio de impacto.
Trátalo como una migración: mapea viejo → nuevo con 301s, actualiza sitemaps, purga cachés y verifica canonical/hreflang después del cambio.
9) ¿Cuál es la prueba más rápida de que la caché es la culpable?
Golpea dos URLs de idioma repetidamente y observa si el contenido cambia o si ambas devuelven el mismo <title> o valor de lang=.
Si deshabilitar la caché (temporalmente) lo arregla, tienes problemas de variación de caché, no que Polylang “duplica páginas”.
10) ¿Debería cambiar de Polylang a otro plugin multilingüe?
Solo si tu problema real es el flujo de trabajo o características faltantes. Si el problema son URLs duplicadas, puedes recrear el mismo desastre con cualquier plugin.
Arregla la verdad de la URL, las canonicals, sitemaps y caché primero. Luego evalúa herramientas.
Pasos prácticos siguientes
La trampa de Polylang no es un solo bug. Es el sistema haciendo exactamente lo que permitiste: múltiples formas de URL, canonicals inconsistentes y cachés que no “hablan” idioma.
No lo solucionas con un toggle de plugin y optimismo.
Haz esto a continuación, en orden:
- Elige un esquema de URL canónico por idioma (incluyendo una decisión firme sobre prefijar o no el idioma por defecto).
- 301 todo lo demás en el borde. Un solo salto. Sin debates.
- Haz que canonical + hreflang cuenten la misma historia en todas las plantillas.
- Corrige la emisión del sitemap para que dejes de alimentar duplicados a los rastreadores.
- Audita las claves de caché; si el idioma no está en la URL, no cachees HTML anónimo hasta que lo esté.
- Pon guardrails: un script de monitorización pequeño, una comprobación de logs para
?lang=y una checklist de despliegue que pruebe el enrutamiento multilingüe como si importara—porque importa.
WordPress multilingüe puede ser estable y rápido. Solo no puede ser vago. Haz que una URL sea la verdad, y que todo lo demás se disculpe con un 301.