Cómo crear sitemaps multilingües en Next.js (Pages Router) v16
Organiza 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 todas las URLs para cada localización rápidamente se vuelve inmanejable. Los sitemaps monolíticos grandes pueden exceder los límites de 50.000 URLs o 50MB definidos por el protocolo de sitemap, invalidándolos. Incluso cuando permanecen dentro de los límites, regenerar y validar un archivo masivo cada vez que el contenido cambia 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 de índice de sitemap. El archivo de índice enumera sitemaps específicos para cada idioma, cada uno conteniendo URLs para una sola localización. 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 es necesario regenerar 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, luego siguen los enlaces a los sitemaps de idiomas individuales.
Pasos
1. Crear 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 utiliza 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 la cabecera Content-Type a text/xml y escribe la respuesta XML directamente.
2. Crear páginas de mapa del sitio específicas para cada idioma
Crea una página de ruta dinámica para generar mapas del sitio 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 idioma de los parámetros de URL y genera XML que contiene solo URLs para ese idioma. Cada mapa del sitio permanece enfocado en el contenido de un solo idioma.
3. Obtener contenido específico por idioma
Reemplaza la función placeholder 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 para el idioma especificado. Devuelve datos estructurados que el generador de mapas del sitio transforma en entradas XML.
4. Añadir páginas estáticas a cada mapa del sitio
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 mapa del sitio de idioma esté completo. Las páginas estáticas utilizan la marca de tiempo actual como su fecha de última modificación.
5. Hacer referencia al índice en robots.txt
Añade la ubicación del índice del 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 individuales de cada idioma. Coloca este archivo en el directorio public.