Internacionalización (i18n) en Next.js App Router: Guía Definitiva con Server Components
Descubre cómo configurar un sitio web multilingüe en Next.js utilizando el App Router y Server Components. Una guía completa desde la estructura de rutas hasta el cambio dinámico de idioma de forma eficiente.
Introducción a la Internacionalización en Next.js 🌍
La internacionalización (i18n) es un pilar fundamental para cualquier aplicación web moderna que busque expandir su audiencia a nivel global. Tradicionalmente, configurar múltiples idiomas en React y frameworks antiguos era una tarea compleja que requería configuraciones manuales de Webpack o routers de terceros pesados. Sin embargo, con el App Router de Next.js y los Server Components, tenemos un ecosistema nativo y optimizado para servir contenido localizado de manera ultrarrápida.
En este tutorial exhaustivo, aprenderás a construir una aplicación multilingüe desde cero utilizando patrones modernos, evitando el sobrecargue de JavaScript en el cliente y optimizando cada página para los motores de búsqueda (SEO multi-idioma).
🛠️ Planificación de la Arquitectura i18n
Antes de escribir código, es crucial entender cómo estructurar las URLs para soportar múltiples idiomas. La estrategia recomendada por la industria y por Next.js es utilizar segmentos de ruta basados en el código de idioma (por ejemplo, /es/about y /en/about).
Ventajas de las URLs basadas en prefijos de idioma:
- 🚀 SEO Amigable: Los motores de búsqueda indexan cada idioma de forma independiente.
- 🎯 Claridad para el Usuario: El usuario siempre sabe en qué idioma está navegando.
- 🌐 Caché Eficiente: Permite cachear páginas renderizadas por servidor basadas en el idioma actual.
⚙️ Paso 1: Configuración Inicial del Proyecto
Comenzaremos creando un nuevo proyecto de Next.js o integrándolo en uno existente. Asegúrate de tener Node.js instalado en tu sistema.
Ejecuta el siguiente comando en tu terminal para crear la aplicación:
npx create-next-app@latest mi-app-i18n --typescript --tailwind --app
Una vez dentro del directorio del proyecto, instala next-intl:
npm install next-intl
📂 Paso 2: Estructura de Archivos y Directorios
Para manejar rutas dinámicas por idioma en el App Router, utilizaremos un parámetro dinámico [locale] que envolverá todas nuestras páginas. La estructura de carpetas se verá así:
mi-app-i18n/
├── messages/
│ ├── en.json
│ └── es.json
├── src/
│ ├── app/
│ │ ├── [locale]/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── i18n.ts
│ └── middleware.ts
Creando los archivos de traducción
Crea una carpeta llamada messages en la raíz de tu proyecto. Aquí guardaremos los diccionarios de texto en formato JSON.
Contenido de messages/es.json:
{
"HomePage": {
"title": "Bienvenido a Nuestra Plataforma Global",
"description": "Desarrolla aplicaciones rápidas y accesibles en múltiples idiomas.",
"button": "Explorar características"
}
}
Contenido de messages/en.json:
{
"HomePage": {
"title": "Welcome to Our Global Platform",
"description": "Build fast and accessible applications in multiple languages.",
"button": "Explore features"
}
}
🔌 Paso 3: Configuración de la Librería i18n
Crea un archivo src/i18n.ts en la raíz de tu carpeta src. Este archivo se encargará de cargar los mensajes correspondientes según el idioma detectado.
import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';
// Lista de idiomas soportados
const locales = ['es', 'en'];
export default getRequestConfig(async ({ locale }) => {
// Validar que el idioma entrante sea soportado
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`../messages/${locale}.json`)).default
};
});
🚦 Paso 4: Implementación del Middleware
El middleware es indispensable para interceptar las peticiones del usuario, detectar su idioma preferido mediante las cabeceras del navegador y redirigir al prefijo correspondiente (ej. / -> /es).
Crea el archivo src/middleware.ts:
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// Lista de todos los idiomas soportados
locales: ['es', 'en'],
// Idioma por defecto si no se puede detectar
defaultLocale: 'es'
});
export const config = {
// Coincidir con todas las rutas excepto archivos estáticos y API
matcher: ['/', '/(es|en)/:path*']
};
🖥️ Paso 5: Creación del Layout y Páginas con Server Components
Ahora configuraremos el layout principal dentro de la carpeta dinámica [locale]. Esto asegurará que el contexto de traducción esté disponible para todos los componentes hijos.
Crea src/app/[locale]/layout.tsx:
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
export default async function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
// Proveer los mensajes al cliente si es necesario
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Construyendo la Página Principal
Crea src/app/[locale]/page.tsx aprovechando los Server Components para traducir el contenido de forma nativa en el servidor:
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm">
<h1 className="text-4xl font-bold mb-4">{t('title')}</h1>
<p className="text-lg mb-6">{t('description')}</p>
<button className="bg-blue-600 text-white px-6 py-3 rounded-lg shadow-md hover:bg-blue-700 transition">
{t('button')}
</button>
</div>
</main>
);
}
🔄 Paso 6: Creación de un Selector de Idioma Interactivo
Para que los usuarios puedan cambiar de idioma manualmente, necesitamos un componente cliente (Client Component) que navegue a la ruta correspondiente.
Crea un componente src/components/LocaleSwitcher.tsx:
'use client';
import { useLocale } from 'next-intl';
import { useRouter, usePathname } from 'navigation'; // O usa next/navigation adaptado
import { useTransition } from 'react';
export default function LocaleSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const [isPending, startTransition] = useTransition();
const onSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const nextLocale = e.target.value;
startTransition(() => {
router.replace(pathname, { locale: nextLocale });
});
};
return (
<div className="inline-flex items-center">
<select
defaultValue={locale}
disabled={isPending}
onChange={onSelectChange}
className="bg-white border border-gray-300 rounded-md px-3 py-1.5 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="es">🇪🇸 Español</option>
<option value="en">🇬🇧 English</option>
</select>
</div>
);
}
🧪 Buenas Prácticas y Optimización SEO
Implementar internacionalización no es solo traducir textos; requiere cuidar aspectos técnicos para garantizar una experiencia de usuario impecable.
Checklist de Verificación SEO:
- ✅ Etiquetas hreflang: Asegúrate de incluir en tus metadatos las URLs alternativas para cada idioma.
- ✅ Atributo lang dinámico: El elemento
<html>siempre debe reflejar el idioma actual (ej.<html lang="es">). - ✅ URLs limpias: Evita parámetros en query strings (
?lang=es) y prioriza las rutas basadas en subdirectorios.
❓ Preguntas Frecuentes (FAQ)
¿Puedo usar archivos de traducción separados para cada página?
Sí, puedes organizar tus archivos JSON por carpetas y módulos dentro de `messages/`, y cargarlos dinámicamente según la página que esté visitando el usuario.¿Cómo manejo la traducción en componentes de servidor frente a componentes de cliente?
En Server Components utilizas getTranslations() o useTranslations() de forma asíncrona o directa, mientras que en Client Components utilizas el hook useTranslations() importado desde next-intl asegurándote de que el proveedor esté activo en el layout.¿Cómo afecta la internacionalización al rendimiento de Next.js?
Con el App Router y Server Components, el impacto en rendimiento es mínimo ya que las traducciones se resuelven directamente en el servidor y solo se envía el HTML resultante al cliente.Conclusión
¡Felicidades! Has configurado con éxito un sistema de internacionalización moderno, escalable y optimizado para SEO en Next.js utilizando el App Router y Server Components. Esta arquitectura te permitirá escalar tu aplicación a decenas de idiomas sin sacrificar el rendimiento ni la experiencia de desarrollo.
Tutoriales relacionados
- Optimización del Bundle en Next.js: Estrategias para Reducir el Tamaño de tu Aplicación Webintermediate15 min
- React Server Components en Next.js 14: Potenciando el Rendimiento y la Experiencia del Desarrolladorintermediate18 min
- ¡Next.js en el Edge! Cómo Usar Edge Functions para Apps Más Rápidas y Globales 🚀intermediate15 min
- ¡Construyendo Widgets Interactivos con Web Components y Next.js App Router!intermediate20 min
- Aprovechando la Carga de Datos en el Cliente con SWR en Next.js App Router ⚡intermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!