Cómo optimizar el SEO de una aplicación multilingüe en Next.js
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/abouty/fr/aboutcomo 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 SEO | Implementación |
|---|---|
| Configuración regional en la URL | createNextMiddleware() con el segmento dinámico [locale] |
Atributo HTML lang | useLocale() en el layout raíz |
| URL canónicas | getLocale() + API de metadatos de Next.js alternates.canonical |
| Etiquetas hreflang | API de metadatos de Next.js alternates.languages con todas las configuraciones regionales compatibles |
| Metadatos traducidos | getGT() para los títulos y las descripciones de las páginas |
| sitemap multilingüe | sitemap.ts con entradas por configuración regional y alternativas de hreflang |
Siguientes pasos
- inicio rápido de gt-next para configurar toda la pila de i18n
- Guía de Middleware para configurar el enrutamiento
- Guía de SSG para generar páginas multilingües de forma estática
- Compatibilidad con RTL para idiomas de derecha a izquierda