Comment créer des sitemaps multilingues dans TanStack Start v1
Organiser les sitemaps par langue pour la scalabilité
Problème
Les sitemaps aident les moteurs de recherche à découvrir et explorer toutes les pages d'un site. Un site multilingue avec des centaines ou des milliers de pages par langue peut rapidement générer des sitemaps énormes. Un seul fichier répertoriant chaque URL pour chaque langue devient ingérable et peut dépasser les limites de 50 000 URL ou 50 Mo définies par le protocole sitemap. Lorsque vous mettez à jour la structure d'une langue, vous devez régénérer et revalider l'intégralité du fichier. Les sitemaps monolithiques volumineux sont difficiles à maintenir, lents à traiter et ne passent pas à l'échelle lorsque vous ajoutez des langues ou du contenu.
Solution
Divisez votre sitemap en plusieurs fichiers et utilisez un fichier d'index de sitemap pour soumettre plusieurs sitemaps à la fois. Créez un index de sitemap de niveau supérieur à /sitemap.xml qui pointe vers des sitemaps distincts spécifiques à chaque langue tels que /sitemap-en.xml et /sitemap-es.xml. Le format XML d'un fichier d'index de sitemap est similaire à un sitemap classique et est défini par le protocole Sitemap. Cela permet de garder les fichiers individuels gérables, de mettre à jour chaque langue indépendamment et de passer à l'échelle efficacement lorsque vous ajoutez de nouvelles langues ou pages.
Étapes
1. Créer une fonction utilitaire pour générer le XML du sitemap
Créez une fonction utilitaire qui génère un XML de sitemap valide à partir d'un tableau d'entrées d'URL.
export function generateSitemapXML(urls: Array<{ loc: string; lastmod?: string; changefreq?: string; priority?: number }>): string {
const entries = urls.map(url => {
let entry = ` <url>
<loc>${url.loc}</loc>`
if (url.lastmod) entry += `
<lastmod>${url.lastmod}</lastmod>`
if (url.changefreq) entry += `
<changefreq>${url.changefreq}</changefreq>`
if (url.priority !== undefined) entry += `
<priority>${url.priority}</priority>`
entry += `
</url>`
return entry
}).join('
')
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${entries}
</urlset>`
}
Cette fonction accepte un tableau d'objets URL et renvoie une chaîne XML correctement formatée avec l'espace de noms et la structure requis.
2. Créer une fonction utilitaire pour générer le XML de l'index de sitemap
Créez une deuxième fonction utilitaire qui génère un index de sitemap pointant vers plusieurs sitemaps enfants.
export function generateSitemapIndexXML(sitemaps: Array<{ loc: string; lastmod?: string }>): string {
const entries = sitemaps.map(sitemap => {
let entry = ` <sitemap>
<loc>${sitemap.loc}</loc>`
if (sitemap.lastmod) entry += `
<lastmod>${sitemap.lastmod}</lastmod>`
entry += `
</sitemap>`
return entry
}).join('
')
return `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${entries}
</sitemapindex>`
}
L'index de sitemap utilise une balise racine <sitemapindex> et inclut une entrée <sitemap> pour chaque sitemap, avec une entrée enfant <loc> pour chaque balise parente.
3. Définir une route serveur pour l'index de sitemap principal
Créez une route serveur à /sitemap.xml qui renvoie l'index de sitemap listant tous les sitemaps spécifiques à chaque langue.
import { createFileRoute } from "@tanstack/react-router";
import { generateSitemapIndexXML } from "~/utils/sitemap";
const SUPPORTED_LOCALES = ["en", "es", "fr", "de"];
const BASE_URL = "https://example.com";
export const Route = createFileRoute("/sitemap")({
server: {
handlers: {
GET: async () => {
const sitemaps = SUPPORTED_LOCALES.map((locale) => ({
loc: `${BASE_URL}/sitemap-${locale}.xml`,
lastmod: new Date().toISOString().split("T")[0],
}));
const xml = generateSitemapIndexXML(sitemaps);
return new Response(xml, {
headers: {
"Content-Type": "application/xml",
"Cache-Control": "public, max-age=3600",
},
});
},
},
},
});
Cette route génère un index qui pointe vers un sitemap par langue et le sert au format XML avec les en-têtes de mise en cache appropriés.
4. Définir des routes serveur pour les sitemaps spécifiques à chaque langue
Créez une route serveur dynamique qui génère un sitemap pour chaque langue en fonction du paramètre de locale.
import { createFileRoute } from "@tanstack/react-router";
import { generateSitemapXML } from "~/utils/sitemap";
const BASE_URL = "https://example.com";
async function getUrlsForLocale(locale: string) {
return [
{ loc: `${BASE_URL}/${locale}`, changefreq: "daily", priority: 1.0 },
{
loc: `${BASE_URL}/${locale}/about`,
changefreq: "monthly",
priority: 0.8,
},
{
loc: `${BASE_URL}/${locale}/contact`,
changefreq: "monthly",
priority: 0.8,
},
];
}
export const Route = createFileRoute("/sitemap-$locale")({
server: {
handlers: {
GET: async ({ params }) => {
const { locale } = params;
const urls = await getUrlsForLocale(locale);
const xml = generateSitemapXML(urls);
return new Response(xml, {
headers: {
"Content-Type": "application/xml",
"Cache-Control": "public, max-age=3600",
},
});
},
},
},
});
Les routes serveur prennent en charge les paramètres de chemin dynamiques de la même manière que TanStack Router, donc un fichier nommé avec $locale crée une route qui accepte un paramètre de locale dynamique. Chaque sitemap spécifique à une langue est généré indépendamment et peut être mis à jour sans affecter les autres langues.
5. Récupérer les URL depuis votre source de données
Remplacez la fonction placeholder getUrlsForLocale par une logique qui récupère les URL réelles depuis votre base de données, CMS ou définitions de routes.
async function getUrlsForLocale(locale: string) {
const pages = await db.page.findMany({
where: { locale, published: true },
select: { slug: true, updatedAt: true },
});
return pages.map((page) => ({
loc: `${BASE_URL}/${locale}/${page.slug}`,
lastmod: page.updatedAt.toISOString().split("T")[0],
changefreq: "weekly",
priority: 0.7,
}));
}
Cet exemple interroge une base de données pour les pages publiées dans la locale donnée et les mappe vers des entrées de sitemap avec métadonnées. Ajustez la requête et la logique de mappage pour correspondre à votre modèle de données et structure d'URL.