Cómo crear sitemaps multilingües en Next.js (Pages Router) v16
Organiza los sitemaps por idioma para escalar
Problema
Los sitemaps ayudan a los motores de búsqueda a descubrir e indexar páginas en un sitio web. Para un sitio multilingüe con cientos o miles de páginas por idioma, un único sitemap que liste cada URL para cada configuración regional se vuelve rápidamente inmanejable. Los sitemaps monolíticos grandes pueden exceder los límites de 50 000 URL o 50 MB definidos por el protocolo de sitemap, haciéndolos inválidos. Incluso cuando permanecen dentro de los límites, regenerar y validar un archivo masivo cada vez que cambia el contenido en un idioma es ineficiente. A medida que el sitio crece y añade más idiomas o páginas, este enfoque no escala.
Solución
Organiza los sitemaps en una jerarquía utilizando un archivo índice de sitemap. El archivo índice lista sitemaps específicos por idioma separados, cada uno conteniendo URLs para una única configuración regional. Esta estructura mantiene los archivos de sitemap individuales manejables y dentro de los límites del protocolo. Cuando el contenido cambia en un idioma, solo necesita regenerarse el sitemap de ese idioma. El enfoque escala naturalmente a medida que se añaden nuevos idiomas: cada uno obtiene su propio sitemap referenciado en el índice. Los motores de búsqueda rastrean primero el índice y luego siguen los enlaces a los sitemaps de idiomas individuales.
Pasos
1. Crea una página de índice de sitemap
Crea una página en el directorio pages para generar el índice de sitemap dinámicamente usando getServerSideProps.
import { GetServerSideProps } from "next";
const SITE_URL = "https://example.com";
const LOCALES = ["en", "es", "fr", "de"];
function generateSitemapIndex(locales: string[]): string {
const sitemapEntries = locales
.map((locale) => {
return `
<sitemap>
<loc>${SITE_URL}/sitemap-${locale}.xml</loc>
<lastmod>${new Date().toISOString()}</lastmod>
</sitemap>`;
})
.join("");
return `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemapEntries}
</sitemapindex>`;
}
export const getServerSideProps: GetServerSideProps = async ({ res }) => {
const sitemap = generateSitemapIndex(LOCALES);
res.setHeader("Content-Type", "text/xml");
res.write(sitemap);
res.end();
return {
props: {},
};
};
export default function SitemapIndex() {}
El índice usa el elemento raíz <sitemapindex> con entradas <sitemap>, cada una conteniendo un hijo <loc> que apunta a un sitemap específico de idioma. La función getServerSideProps establece el encabezado Content-Type en text/xml y escribe la respuesta XML directamente.
2. Crea páginas de sitemap específicas por idioma
Crea una página de ruta dinámica para generar sitemaps individuales para cada idioma.
import { GetServerSideProps } from "next";
const SITE_URL = "https://example.com";
interface PageData {
slug: string;
lastModified: string;
}
async function getPagesByLocale(locale: string): Promise<PageData[]> {
return [
{ slug: "about", lastModified: "2024-01-15" },
{ slug: "contact", lastModified: "2024-01-20" },
];
}
function generateSitemap(locale: string, pages: PageData[]): string {
const urlEntries = pages
.map((page) => {
return `
<url>
<loc>${SITE_URL}/${locale}/${page.slug}</loc>
<lastmod>${page.lastModified}</lastmod>
</url>`;
})
.join("");
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urlEntries}
</urlset>`;
}
export const getServerSideProps: GetServerSideProps = async ({
params,
res,
}) => {
const locale = params?.locale as string;
const pages = await getPagesByLocale(locale);
const sitemap = generateSitemap(locale, pages);
res.setHeader("Content-Type", "text/xml");
res.write(sitemap);
res.end();
return {
props: {},
};
};
export default function LocaleSitemap() {}
La ruta dinámica extrae el locale de los parámetros de URL y genera XML que contiene solo URLs para ese idioma. Cada sitemap permanece enfocado en el contenido de un único locale.
3. Obtener contenido específico del locale
Reemplaza la función de marcador de posición getPagesByLocale con tu fuente de datos real.
async function getPagesByLocale(locale: string): Promise<PageData[]> {
const response = await fetch(
`https://api.example.com/pages?locale=${locale}`,
);
const data = await response.json();
return data.pages.map((page: any) => ({
slug: page.slug,
lastModified: page.updatedAt,
}));
}
Esta función consulta tu CMS, base de datos o API para recuperar páginas del locale especificado. Devuelve datos estructurados que el generador de sitemap transforma en entradas XML.
4. Añadir páginas estáticas a cada sitemap
Incluye rutas estáticas que existen en todos los idiomas junto con el contenido dinámico.
function generateSitemap(locale: string, pages: PageData[]): string {
const staticPages = [
{ slug: "", lastModified: new Date().toISOString() },
{ slug: "about", lastModified: new Date().toISOString() },
];
const allPages = [...staticPages, ...pages];
const urlEntries = allPages
.map((page) => {
const path = page.slug ? `/${locale}/${page.slug}` : `/${locale}`;
return `
<url>
<loc>${SITE_URL}${path}</loc>
<lastmod>${page.lastModified}</lastmod>
</url>`;
})
.join("");
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urlEntries}
</urlset>`;
}
Combinar páginas estáticas y dinámicas asegura que cada sitemap de idioma esté completo. Las páginas estáticas usan la marca de tiempo actual como su fecha de última modificación.
5. Referenciar el índice en robots.txt
Añade la ubicación del índice de sitemap a tu archivo robots.txt para que los motores de búsqueda lo descubran.
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
Solo necesitas listar el archivo de índice; los motores de búsqueda seguirán automáticamente los enlaces a los sitemaps de idiomas individuales. Coloca este archivo en el directorio public.