
# El error de canonical que esconde tus traducciones

agentready.md se publica en diez idiomas con prefijo en la ruta. Cada página existe en `/about`, `/es/about`, `/ja/about` y siete más. Durante meses, las nueve traducciones llevaban esta misma línea en su `<head>`:

```html
<link rel="canonical" href="https://agentready.md/about">
```

`/es/about` le estaba diciendo a los buscadores: soy un duplicado de la página en inglés, indexa esa. Lo mismo `/ja/about` y lo mismo las otras siete. Diez idiomas, uno indexable.

## Qué dice de verdad un canonical

El `rel="canonical"` no es una pista sobre qué URL queda más limpia. Es una afirmación: esta página y la que nombro somos la misma, y la buena para el índice es la otra. Los enlaces, las señales de posicionamiento y la propia entrada en el índice se le acreditan al destino. Esta página deja de competir.

Es justo lo que quieres cuando `/producto?color=azul` apunta a `/producto`, y justo lo que no quieres en una traducción. Una página en español no es un duplicado de la inglesa: es otra página, para otro lector, y tiene que posicionar por su cuenta en las búsquedas en español.

## Por qué sobrevivió tanto tiempo

Nuestro router quita el prefijo de idioma antes de enrutar, así que una petición a `/es/about` llega al handler como `/about` y el idioma viaja aparte. Es un diseño sensato, porque las rutas se declaran una vez y valen para todos los idiomas, pero deja `request.url` sin prefijo, y la plantilla del `<head>` construía el canonical con esa ruta pelada.

En inglés el fallo produce el resultado correcto, porque el inglés no lleva prefijo. Todas las páginas que miras mientras desarrollas en tu idioma están bien. Solo se rompe en los nueve que no lees.

El arreglo es una llamada, devolverle el prefijo antes de imprimir la URL:

```ejs
<%# request.url ya ha perdido el prefijo /es/ — localizedUrl se lo devuelve. %>
<% const canonicalHref = localizedUrl(canonicalPath); %>
<link rel="canonical" href="<%= baseUrl %><%= canonicalHref %>">
```

## La regla: cada versión es canonical de sí misma

Canonical autorreferente, siempre. `/es/about` declara `/es/about`. `/ja/about` declara `/ja/about`. Con las traducciones no hay excepción. Y si dos versiones se parecen muchísimo, la misma ficha de producto con tres palabras cambiadas, da igual: el idioma no es duplicación.

## hreflang: cada versión las lista todas

El canonical dice qué página indexar. El `hreflang` dice a qué lector enseñar cuál de las indexadas. Hacen falta los dos, y hay dos reglas del `hreflang` que casi todo el mundo se salta.

**Cada versión las lista todas, incluida ella misma.** Diez idiomas son diez enlaces `alternate` en las diez páginas, con la autorreferencia dentro. Olvidarse de esa autorreferencia es el defecto más común en conjuntos por lo demás correctos.

**Los enlaces tienen que ser recíprocos.** Si la página en inglés nombra a la española y la española no le devuelve el nombre, el buscador descarta la relación, y a menudo el conjunto entero, porque no puede verificarla. Cualquiera puede decir que es la traducción de otro. Solo la otra página lo confirma.

Genéralos desde una lista en lugar de mantener diez:

```ejs
<% ['en','es','fr','de','pt','it','ja','zh','ko','ru'].forEach(l => { %>
<link rel="alternate" hreflang="<%= l %>" href="<%= baseUrl %><%= localizedUrl(canonicalPath, l) %>">
<% }) %>
<link rel="alternate" hreflang="x-default" href="<%= baseUrl %><%= canonicalPath %>">
```

Usa el código de idioma a secas (`es`) cuando una sola versión sirve a todos los hablantes. Añade región (`es-ES`, `pt-BR`) solo si de verdad publicas versiones distintas por mercado; inventarte regiones que no tienes parte el conjunto para nada.

## x-default no es otra traducción

El `x-default` señala qué versión servirle a alguien cuyo idioma no publicas. Si buscan en neerlandés y tú no tienes neerlandés, se llevan lo que apunte ahí.

No es «el idioma más importante» ni una entrada más de la rotación. El nuestro se queda en la URL inglesa sin prefijo, que ya es el respaldo de toda la web. Si lo apuntas a una página con idioma propio, `/es/about`, mandas a español a todo lector para el que no tienes traducción, y eso es peor que el defecto.

## Compruébalo en una línea

El fallo entero se ve desde la terminal. Recorre tus idiomas e imprime lo que declara cada página:

```bash
for p in "" es/ fr/ de/ pt/ it/ ja/ zh/ ko/ ru/; do
  printf '%-4s ' "${p:-en}"
  curl -s "https://ejemplo.com/${p}about" | grep -o '<link rel="canonical"[^>]*>'
done
```

La salida correcta nombra en cada línea su propia ruta:

```
en   <link rel="canonical" href="https://ejemplo.com/about">
es   <link rel="canonical" href="https://ejemplo.com/es/about">
fr   <link rel="canonical" href="https://ejemplo.com/fr/about">
```

Diez líneas con la misma URL son el fallo de este artículo. Pásalo por las plantillas que hayas tocado y no solo por la portada: el canonical suele construirse por layout, y una web con tres layouts puede estar bien en dos.

## Las otras formas de romperlo

**Parámetros de seguimiento dentro del canonical.** Si construyes el canonical con la URL de la petición tal cual, quien llegue con `?utm_source=newsletter` recibe un canonical que apunta a `?utm_source=newsletter`. Cada vez que alguien comparte el enlace nace una URL nueva que se declara canonical, o sea un conjunto sin límite de URLs sirviendo una sola página. Constrúyelo desde el pathname y devuelve solo los parámetros que identifican una página distinta de verdad, un listado filtrado o una paginación, y en un orden fijo, para que una página no pueda escribir su propio nombre de dos maneras.

**Canonical apuntando a una redirección.** Si `/es/about` declara `/es/about/` y eso hace un 301 de vuelta a `/es/about`, le has dado al rastreador un bucle que resolver y un motivo para desconfiar de la señal. Apunta a la URL que devuelve `200`.

**Dos canonical que se contradicen.** El `<link>` del HTML y una cabecera HTTP `Link: <...>; rel="canonical"` son igual de válidos, y los meten CDNs, plugins y proxies inversos. Cuando no coinciden, el resultado queda al azar y casi nunca es el que querías. Mira también las cabeceras:

```bash
curl -sI https://ejemplo.com/es/about | grep -i "^link:"
```

Que no salga nada es la respuesta buena, salvo que la hayas puesto tú a propósito.

[Analiza tu web](/es) · [Generador de meta tags](/es/tools/meta-tags-generator) · [Checklist de preparación para IA](/es/tools/ai-readiness-checklist)
