Volver

Cómo optimizar el SEO para una app multilingüe de Next.js

Team avatarTeam
guideseointernationalizationnextjsi18nroutingmetadata

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

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

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

  • Google puede indexar solo una versión en un idioma e ignorar las demás
  • A los usuarios que buscan en español se les muestra la página en inglés
  • Penalizaciones por contenido duplicado porque Google ve /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 clave que debes resolver, y esta guía cubre todos usando gt-next.


1. Enrutamiento de URL según la configuración regional

La base del SEO multilingüe es contar con 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 distinto.

Esto significa la 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)

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

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

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

Luego, crea el middleware en la raíz del 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 genera automáticamente URL con prefijo de configuración regional. De forma predeterminada, la configuración regional predeterminada (p. ej., inglés) no lleva prefijo — /about se mantiene simple, mientras que los usuarios en español ven /es/about y los usuarios en francés ven /fr/about.


2. Configurar el atributo lang del 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 simples y a la vez más importantes que puedes hacer para 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 clasifican el idioma con menos confianza.

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 en formato 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 de idioma debe apuntar a sí misma como URL 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 la página en inglés.

En Next.js, las URL canónicas se configuran 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 lo necesario:

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. Les 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 de 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 añadir 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,
    },
  };
}

Después, úsalo en cualquier página:

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

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

Esto genera tanto la etiqueta canonical como 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 aparecen en inglés en una página en francés, es menos probable que los usuarios hagan clic, y Google 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 da títulos y descripciones localizados en los resultados de búsqueda, lo que mejora significativamente la tasa de clics en las búsquedas en otros idiomas.


6. Mapas del sitio multilingües

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

Next.js admite mapas del sitio 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 mapa del sitio con una entrada por página para cada configuración regional, y cada entrada incluye anotaciones hreflang que apuntan a todas las versiones en cada idioma.


Lista de verificación

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

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 títulos y descripciones de página
Mapa del sitio multilingüesitemap.ts con entradas por configuración regional y alternativas hreflang

Próximos pasos