Comment créer des sitemaps multilingues dans TanStack Start v1
Organiser les sitemaps par langue pour une meilleure évolutivité
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 d'énormes sitemaps. Un fichier unique répertoriant chaque URL pour chaque langue devient difficile à gérer et peut dépasser les limites de 50 000 URL ou de 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 s'adaptent pas à l'ajout de langues ou de 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 premier niveau à /sitemap.xml qui pointe vers des sitemaps 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 ordinaire 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 s'adapte bien à l'ajout de nouvelles langues ou pages.
Étapes
1. Créer un utilitaire pour générer du XML de sitemap
Construisez une fonction utilitaire qui génère du XML de sitemap valide à partir d'un tableau d'entrées 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 un helper pour générer un fichier XML d'index de sitemap
Construisez une seconde 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 parent.
3. Définir une route serveur pour l'index principal du sitemap
Créez une route serveur à /sitemap.xml qui renvoie l'index de sitemap listant tous les sitemaps spécifiques aux langues.
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 en XML avec des en-têtes de mise en cache appropriés.
4. Définir des routes serveur pour les sitemaps spécifiques aux langues
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 URLs depuis votre source de données
Remplacez la fonction getUrlsForLocale par une logique qui récupère les URLs 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 transforme en entrées de sitemap avec des métadonnées. Ajustez la requête et la logique de transformation pour correspondre à votre modèle de données et à la structure de vos URLs.