Volver

Cómo optimizar el SEO de una aplicación multilingüe en Next.js

Team avatarTeam
guideseointernationalizationnextjsi18nroutingmetadata

Por qué el SEO multilingüe requiere atención especial

La mayoría de los desarrolladores que añaden i18n a su aplicación Next.js se centran en traducir la interfaz de usuario y dan el trabajo por terminado. Pero si los motores de búsqueda no pueden encontrar, indexar y asociar correctamente las versiones por idioma, todo ese trabajo de traducción queda invisible.

Un sitio multilingüe sin una configuración SEO adecuada tiene varios problemas:

  • Google puede indexar solo una versión en un idioma e ignorar el resto
  • Los usuarios que buscan en español reciben la página en inglés
  • Penalizaciones por contenido duplicado porque Google considera /en/about y /fr/about como la misma página
  • Se muestra el idioma incorrecto en los fragmentos de los resultados de búsqueda

La buena noticia: hacer bien el SEO multilingüe en Next.js no es complicado. Hay seis aspectos que debes configurar correctamente, y esta guía explica todos ellos usando gt-next.


1. Enrutamiento de URL por configuración regional

La base del SEO multilingüe es tener URL distintas para cada idioma. Los motores de búsqueda necesitan URL separadas y rastreables para indexar de forma independiente cada versión en un idioma.

Esto significa configuración regional en la URL — no cookies, no parámetros de consulta ni solo detección de Accept-Language.

✅ generaltranslation.com/en/about
✅ generaltranslation.com/fr/about
✅ generaltranslation.com/es/about

❌ generaltranslation.com/about?lang=fr
❌ generaltranslation.com/about (with locale in a cookie)

Configurar el enrutamiento por configuración regional con gt-next

Primero, anida tus páginas bajo un segmento dinámico [locale]:

app/
└── [locale]/
    ├── layout.tsx
    ├── page.tsx
    └── about/
        └── page.tsx

Luego, crea el middleware en el directorio raíz de tu proyecto (proxy.ts para Next.js 16+ o middleware.ts para Next.js 15 y versiones anteriores):

import { createNextMiddleware } from 'gt-next/middleware';

export default createNextMiddleware();

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next).*)'],
};

Esto te proporciona automáticamente URL con el prefijo de la configuración regional. Por defecto, la configuración regional predeterminada (p. ej., inglés) no lleva prefijo: /about se mantiene limpio, mientras que los usuarios de español ven /es/about y los usuarios de francés ven /fr/about.


2. Establecer el atributo lang en HTML

El atributo lang de la etiqueta <html> indica a los navegadores y a los motores de búsqueda en qué idioma está la página. Es una de las cosas más sencillas y con mayor impacto que puedes hacer por la accesibilidad y el SEO.

Sin él, los lectores de pantalla tienen que deducir el idioma (y a menudo se equivocan), y los motores de búsqueda tienen menos confianza en su clasificación del idioma.

gt-next proporciona el hook useLocale, lo que hace que esto sea muy sencillo en tu layout raíz:

import { useLocale, GTProvider } from 'gt-next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = useLocale();

  return (
    <html lang={locale}>
      <body>
        <GTProvider>
          {children}
        </GTProvider>
      </body>
    </html>
  );
}

useLocale devuelve el código de configuración regional según BCP 47 (p. ej., en-US, ar, zh-Hans).


3. URL canónicas

Las etiquetas canónicas indican a los motores de búsqueda qué URL es la versión "principal" de una página. En los sitios multilingües, cada versión en un idioma debe apuntarse a sí misma como la canónica:

<!-- En /fr/about -->
<link rel="canonical" href="https://example.com/fr/about" />

Esto evita que los motores de búsqueda consideren tu página en francés como un duplicado de tu página en inglés.

En Next.js, las URL canónicas se definen mediante la API de metadatos. Combínala con getLocale de gt-next para generar la URL canónica correcta para cada configuración regional:

import { getLocale } from 'gt-next/server';

const BASE_URL = 'https://example.com';

export async function generateMetadata() {
  const locale = await getLocale();

  return {
    alternates: {
      canonical: `${BASE_URL}/${locale}/about`,
    },
  };
}

export default function AboutPage() {
  return <h1>About Us</h1>;
}

Para la configuración regional predeterminada sin prefijo, ajusta según corresponda:

import { getLocale, getDefaultLocale } from 'gt-next/server';

export async function generateMetadata() {
  const locale = await getLocale();
  const defaultLocale = getDefaultLocale();
  const path = '/about';

  const prefix = locale === defaultLocale ? '' : `/${locale}`;

  return {
    alternates: {
      canonical: `${BASE_URL}${prefix}${path}`,
    },
  };
}

4. Etiquetas hreflang

Las etiquetas hreflang son la señal de SEO multilingüe más importante. Indican a los motores de búsqueda: "esta página existe en estos otros idiomas, y estas son las URL."

Sin hreflang, Google tiene que adivinar qué versión en cada idioma mostrar en los resultados de búsqueda, y a menudo se equivoca.

<link rel="alternate" hreflang="en" href="https://example.com/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about" />
<link rel="alternate" hreflang="es" href="https://example.com/es/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about" />

La etiqueta x-default indica a los motores de búsqueda qué URL deben mostrar cuando ninguno de los idiomas especificados coincide con el del usuario.

En Next.js, puedes agregar hreflang mediante la propiedad alternates.languages de la API de metadatos. Aquí tienes un helper reutilizable que funciona con gt-next:

import { getLocale, getDefaultLocale } from 'gt-next/server';

const BASE_URL = 'https://example.com';
const SUPPORTED_LOCALES = ['en', 'fr', 'es'];

export async function getI18NMetadata(path: string) {
  const locale = await getLocale();
  const defaultLocale = getDefaultLocale();

  const getUrl = (loc: string) => {
    const prefix = loc === defaultLocale ? '' : `/${loc}`;
    return `${BASE_URL}${prefix}${path}`;
  };

  const languages: Record<string, string> = {};
  for (const loc of SUPPORTED_LOCALES) {
    languages[loc] = getUrl(loc);
  }
  languages['x-default'] = getUrl(defaultLocale);

  return {
    alternates: {
      canonical: getUrl(locale),
      languages,
    },
  };
}

Luego, úsalo en cualquier página:

import { getI18NMetadata } from '@/lib/i18n-metadata';

export async function generateMetadata() {
  return await getI18NMetadata('/about');
}

Esto genera la etiqueta canónica y todas las etiquetas hreflang en una sola llamada.


5. Metadatos traducidos

Los motores de búsqueda muestran el título y la descripción de tu página en los resultados. Si están en inglés en una página en francés, es menos probable que los usuarios hagan clic, y Google incluso puede rebajar la posición del resultado.

Usa la función getGT de gt-next para traducir las cadenas de metadatos:

import { getGT } from 'gt-next/server';
import { getI18NMetadata } from '@/lib/i18n-metadata';

export async function generateMetadata() {
  const t = await getGT();
  const i18nMeta = await getI18NMetadata('/about');

  return {
    title: t('About Us'),
    description: t('Learn about our mission and team.'),
    ...i18nMeta,
  };
}

Esto te proporciona títulos y descripciones localizados en los resultados de búsqueda, lo que mejora significativamente las tasas de clics para las búsquedas en idiomas distintos del inglés.


6. Sitemaps multilingües

Un sitemap ayuda a los motores de búsqueda a descubrir todas tus páginas, incluidas las versiones en cada idioma. En los sitios multilingües, también debes incluir anotaciones hreflang en tu sitemap.

Next.js admite sitemaps programáticos mediante un archivo sitemap.ts:

import { MetadataRoute } from 'next';

const BASE_URL = 'https://example.com';
const LOCALES = ['en', 'fr', 'es'];
const DEFAULT_LOCALE = 'en';
const PAGES = ['/', '/about', '/blog', '/contact'];

export default function sitemap(): MetadataRoute.Sitemap {
  return PAGES.flatMap((path) => {
    const getUrl = (locale: string) => {
      const prefix = locale === DEFAULT_LOCALE ? '' : `/${locale}`;
      return `${BASE_URL}${prefix}${path === '/' ? '' : path}`;
    };

    const languages: Record<string, string> = {};
    for (const locale of LOCALES) {
      languages[locale] = getUrl(locale);
    }

    return LOCALES.map((locale) => ({
      url: getUrl(locale),
      lastModified: new Date(),
      alternates: { languages },
    }));
  });
}

Esto genera un sitemap con una entrada por página y por configuración regional, y cada entrada incluye anotaciones hreflang que apuntan a todas las versiones de idioma.


Lista de comprobación

Aquí tienes un resumen rápido de todo lo tratado:

Requisito de SEOImplementación
Configuración regional en la URLcreateNextMiddleware() con el segmento dinámico [locale]
Atributo HTML languseLocale() en el layout raíz
URL canónicasgetLocale() + API de metadatos de Next.js alternates.canonical
Etiquetas hreflangAPI de metadatos de Next.js alternates.languages con todas las configuraciones regionales compatibles
Metadatos traducidosgetGT() para los títulos y las descripciones de las páginas
sitemap multilingüesitemap.ts con entradas por configuración regional y alternativas de hreflang

Siguientes pasos