Validateur de sitemap XML
Toutes les règles du protocole. Sans limite d’URL ni inscription.
Tout s'exécute dans cet onglet. Rien de ce que vous collez n'est envoyé, journalisé ni transmis où que ce soit. Ouvrez votre panneau réseau et vérifiez.
Collez votre sitemap ci-dessus, ou saisissez son adresse et appuyez sur Récupérer : toutes les règles du protocole sitemaps.org sont vérifiées d’un coup, le nombre d’URL face à la limite de 50 000, la taille décompressée face à la limite de 50 Mo, et le format de chaque loc, lastmod, changefreq et priority. Les fichiers d’index de sitemaps ont leur propre jeu de règles.
La plupart des gens arrivent de la Search Console avec « Impossible de lire le sitemap » ou « Le sitemap est lisible mais comporte des erreurs », et ni l’un ni l’autre ne nomme une ligne. Ici, chaque constat cite la valeur incriminée, mentionne la règle et dit quoi écrire à la place.
Deux choses le distinguent du reste de cette recherche. Rien n’est envoyé, donc un sitemap de préproduction listant des pages non encore publiées reste sur votre machine. Et les contrôles qui n’ont pas pu être effectués sont signalés comme non effectués plutôt que comme réussis.
Les deux limites strictes
Un sitemap unique peut lister 50 000 URL et peser 50 Mo décompressé, ce que le protocole exprime comme 52 428 800 octets. Les deux sont vérifiés, et la taille en octets est indiquée que vous en soyez proche ou non, car c’est le seul chiffre que vous ne pouvez pas juger en regardant le fichier.
Gzip n’achète aucune marge : la limite s’applique après décompression, donc un sitemap.xml.gz de 3 Mo qui se déplie en 61 Mo la dépasse. Un index de sitemaps porte les deux mêmes limites. Les deux préviennent à quatre-vingt-dix pour cent, car un sitemap bâti sur un catalogue qui grandit franchit la ligne un mardi ordinaire.
- 50 000 entrées <url> par fichier de sitemap.
- 52 428 800 octets décompressés, que vous gzippiez ou non.
- 2 048 caractères par valeur de <loc>.
- 50 000 entrées <sitemap> par index de sitemaps.
L’espace de noms doit être exact
La racine est <urlset>, ou <sitemapindex> pour un index, et elle doit déclarer http://www.sitemaps.org/schemas/sitemap/0.9. Trois détails piègent le monde : c’est http et non https, même sur un site en https, il n’y a pas de barre oblique finale, et la version est 0.9 et l’a toujours été.
Trompez-vous et rien ne proteste bruyamment. Le fichier reste bien formé et s’ouvre toujours dans un navigateur. Les moteurs de recherche ne reconnaissent simplement pas des éléments dans le mauvais espace de noms : ils lisent donc votre sitemap comme un document ne contenant rien.
Mélanger des enfants <url> et <sitemap> est une erreur, car un fichier est soit un sitemap soit un index. L’encodage déclaré doit être UTF-8. Des éléments d’extension, on vérifie seulement qu’ils ont un préfixe déclaré ; les règles image, vidéo et news ne sont pas validées ici.
lastmod, changefreq et priority
lastmod doit être un W3C Datetime. AAAA-MM-JJ est valide à lui seul ; un horodatage complet est valide s’il porte un fuseau, comme 2026-03-14T09:30:00+00:00. Une espace à la place du T, un horodatage Unix, 14/03/2026, ou un horodatage sans fuseau sont tous des erreurs.
Un second contrôle de lastmod tourne, qu’aucun autre validateur gratuit ne fait. Si plus de quatre entrées en portent un et que toutes les valeurs sont identiques, vous recevez un avertissement : votre générateur estampille l’heure de build plutôt que la date de modification du contenu. Google n’utilise lastmod que s’il est systématiquement et vérifiablement exact ; ce motif lui apprend donc à ignorer le champ.
changefreq accepte sept valeurs en minuscules : always, hourly, daily, weekly, monthly, yearly, never. priority doit être comprise entre 0.0 et 1.0. Les deux sont aussi signalées à titre d’information dès qu’elles sont présentes, car Google indique dans sa propre documentation qu’il les ignore.
Même hôte, même répertoire, et récupération par URL
Un sitemap situé à https://example.com/catalog/sitemap.xml peut lister des URL sous https://example.com/catalog/ et ne peut pas lister des URL sous https://example.com/images/. Même hôte, même schéma, dans son propre répertoire ou en dessous. Ces règles réclament l’adresse à laquelle le sitemap sera publié, que le fichier ne contient pas.
C’est à cela que sert le champ URL, et vous pouvez saisir une adresse sans rien récupérer. Saisissez-la et trois contrôles s’activent : un autre hôte est une erreur, un schéma différent est une erreur, et un loc hors du répertoire propre du sitemap est un avertissement. Laissez-le vide et les trois apparaissent comme non effectués plutôt que de passer en silence.
Récupérer demande le fichier depuis votre navigateur directement à votre serveur, sans proxy intermédiaire : CORS le bloque donc chez beaucoup d’hébergeurs. Quand c’est le cas, vous obtenez la raison et une commande curl à lancer vous-même.
Vérifier un sitemap en code
Cela mérite d’être branché dans une étape de build, pour qu’un sitemap ayant dépassé la limite de 50 000 URL fasse échouer votre déploiement plutôt que votre indexation. Chaque exemple compte les URL, mesure la taille décompressée et vérifie lastmod.
// Node 18 or later. npm i @xmldom/xmldom
import { DOMParser } from '@xmldom/xmldom';
const SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
const MAX_URLS = 50_000;
const MAX_BYTES = 52_428_800;
const response = await fetch('https://example.com/sitemap.xml');
const xml = await response.text();
const bytes = Buffer.byteLength(xml, 'utf8');
// xmldom does not resolve external entities, so there is no XXE surface here.
const doc = new DOMParser().parseFromString(xml, 'text/xml');
const root = doc.documentElement;
if (root.namespaceURI !== SITEMAP_NS) {
throw new Error('Namespace is "' + root.namespaceURI + '", expected "' + SITEMAP_NS + '"');
}
const locs = doc.getElementsByTagNameNS(SITEMAP_NS, 'loc');
console.log(locs.length + ' URLs, ' + bytes + ' bytes uncompressed');
if (locs.length > MAX_URLS) throw new Error('Over the 50,000 URL limit');
if (bytes > MAX_BYTES) throw new Error('Over the 50 MB limit');
const lastmods = doc.getElementsByTagNameNS(SITEMAP_NS, 'lastmod');
for (let i = 0; i < lastmods.length; i++) {
const v = lastmods.item(i).textContent.trim();
// W3C Datetime: a bare date, or a timestamp that carries a zone.
const ok = /^\d{4}-\d{2}-\d{2}$/.test(v)
|| (/^\d{4}-\d{2}-\d{2}T/.test(v) && /(Z|[+-]\d{2}:\d{2})$/.test(v));
if (!ok) console.error('Invalid lastmod: ' + v);
}# pip install lxml requests
import re
import sys
import requests
from lxml import etree
SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9'
MAX_URLS, MAX_BYTES = 50_000, 52_428_800
raw = requests.get('https://example.com/sitemap.xml', timeout=30).content
# The three flags are the safe form. lxml will otherwise expand entities and
# fetch a DTD the document references.
parser = etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False)
root = etree.fromstring(raw, parser)
if etree.QName(root).namespace != SITEMAP_NS:
sys.exit('Wrong namespace: %s' % etree.QName(root).namespace)
locs = root.findall('{%s}url/{%s}loc' % (SITEMAP_NS, SITEMAP_NS))
print('%d URLs, %d bytes uncompressed' % (len(locs), len(raw)))
if len(locs) > MAX_URLS:
sys.exit('Over the 50,000 URL limit')
if len(raw) > MAX_BYTES:
sys.exit('Over the 50 MB limit')
W3C = re.compile(r'^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?$')
for lm in root.iter('{%s}lastmod' % SITEMAP_NS):
if not W3C.match((lm.text or '').strip()):
print('Invalid lastmod:', lm.text)
# The protocol publishes an XSD, which lxml can check structure against:
# schema = etree.XMLSchema(etree.parse('sitemap.xsd'))
# schema.assertValid(root.getroottree())import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
final String NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
byte[] bytes = HttpClient.newHttpClient()
.send(HttpRequest.newBuilder(URI.create("https://example.com/sitemap.xml")).build(),
HttpResponse.BodyHandlers.ofByteArray())
.body();
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
// Without setNamespaceAware(true) every namespace lookup below returns nothing.
// This is the single most common reason sitemap parsing code silently finds zero URLs.
f.setNamespaceAware(true);
f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
f.setXIncludeAware(false);
Document doc = f.newDocumentBuilder().parse(new ByteArrayInputStream(bytes));
NodeList locs = doc.getElementsByTagNameNS(NS, "loc");
System.out.printf("%d URLs, %d bytes uncompressed%n", locs.getLength(), bytes.length);
if (locs.getLength() > 50_000) System.err.println("Over the 50,000 URL limit");
if (bytes.length > 52_428_800) System.err.println("Over the 50 MB limit");
NodeList lastmods = doc.getElementsByTagNameNS(NS, "lastmod");
for (int i = 0; i < lastmods.getLength(); i++) {
String v = lastmods.item(i).getTextContent().trim();
try {
if (v.length() == 10) LocalDate.parse(v);
else OffsetDateTime.parse(v); // rejects a timestamp with no zone
} catch (DateTimeParseException e) {
System.err.println("Invalid lastmod: " + v);
}
}using System.Globalization;
using System.Xml;
const string Ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
// XmlReader streams, so a 50 MB sitemap never becomes a 400 MB DOM.
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit, // external entities are never fetched
XmlResolver = null,
IgnoreWhitespace = true,
};
string[] w3c =
{
"yyyy-MM-dd",
"yyyy-MM-ddTHH:mmK",
"yyyy-MM-ddTHH:mm:ssK",
"yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
};
byte[] bytes = await new HttpClient().GetByteArrayAsync("https://example.com/sitemap.xml");
int urls = 0;
using var stream = new MemoryStream(bytes);
using var reader = XmlReader.Create(stream, settings);
while (reader.Read())
{
if (reader.NodeType != XmlNodeType.Element || reader.NamespaceURI != Ns) continue;
if (reader.LocalName == "loc")
{
urls++;
}
else if (reader.LocalName == "lastmod")
{
var v = reader.ReadElementContentAsString().Trim();
// TryParse would accept 03/14/2026. TryParseExact against the four
// permitted layouts is what the protocol actually asks for.
if (!DateTimeOffset.TryParseExact(v, w3c, CultureInfo.InvariantCulture,
DateTimeStyles.None, out _))
{
Console.Error.WriteLine("Invalid lastmod: " + v);
}
}
}
Console.WriteLine(urls + " URLs, " + bytes.Length + " bytes uncompressed");
if (urls > 50_000) Console.Error.WriteLine("Over the 50,000 URL limit");
if (bytes.Length > 52_428_800) Console.Error.WriteLine("Over the 50 MB limit");# The protocol publishes an XSD, so xmllint can check the structure offline.
curl -sL https://example.com/sitemap.xml -o sitemap.xml
curl -sO https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd
xmllint --noout --nonet --schema sitemap.xsd sitemap.xml
# The two hard limits:
xmllint --nonet --xpath 'count(//*[local-name()="loc"])' sitemap.xml # cap 50,000
wc -c < sitemap.xml # cap 52,428,800
# A gzipped sitemap must be decompressed before either check:
curl -sL https://example.com/sitemap.xml.gz | gunzip > sitemap.xml
# Pull every URL out, one per line, for a crawler or a spreadsheet:
xmllint --nonet --xpath '//*[local-name()="loc"]/text()' sitemap.xml | tr -s '\n'Les drapeaux de sécurité ne sont pas décoratifs : Java, Python et C# résolvent tous les entités externes sauf indication contraire, et un sitemap est en général un fichier que vous n’avez pas écrit, arrivé par le réseau depuis un plugin. Le drapeau --nonet de xmllint fait le même travail.
Questions fréquentes
La Search Console dit que mon sitemap a des erreurs, mais cet outil le trouve correct. Pourquoi ?
Les deux regardent des choses différentes. Cette page vérifie le fichier : structure, espace de noms, limites, dates, ainsi que les règles d’hôte et de répertoire dès que vous fournissez l’adresse du sitemap. La Search Console vérifie en plus ce que seul un robot peut voir, à commencer par le fait que le fichier ait été joignable.
« Impossible de récupérer » signifie d’ordinaire un 404, une chaîne de redirections, une règle de robots.txt couvrant le chemin du sitemap, ou une page d’erreur HTML servie avec un statut 200. Récupérer l’URL ici montre le vrai code de statut. L’autre décalage fréquent : les URL sont valides mais les pages derrière sont en noindex ou ont disparu, ce qui est un problème d’exploration.
Google utilise-t-il changefreq et priority ?
Non. Google indique dans sa documentation sur les sitemaps qu’il ignore les deux. Cet outil signale leur présence à titre d’information plutôt que comme une erreur, car ce sont des éléments légaux et ils ne font pas de mal.
Concrètement : aucun agencement de valeurs de priority ne fera explorer une page avant une autre, et un changefreq réglé sur hourly n’augmentera pas votre fréquence d’exploration. Si vous maintenez du code qui les calcule, ce code ne gagne pas sa place. lastmod est l’exception, et c’est le seul élément facultatif qu’il vaut la peine de soigner.
Quel format doit avoir lastmod ?
W3C Datetime, un sous-ensemble d’ISO 8601. La forme valide la plus simple est une date seule, 2026-03-14. Si vous incluez une heure, vous devez inclure un fuseau : 2026-03-14T09:30:00Z et 2026-03-14T09:30:00+00:00 sont tous deux corrects.
Les formes rejetées sont celles que les bases de données et les gabarits produisent naturellement. Une espace à la place du T est invalide, un horodatage sans fuseau est invalide, tout comme un nombre d’époque Unix ou une date au format jour d’abord telle que 14/03/2026. Si les mauvaises dates viennent d’un plugin plutôt que de votre code, la valeur citée dans l’erreur permet en général d’identifier lequel.
Combien d’URL un sitemap peut-il contenir ?
50 000, et il doit aussi rester sous 52 428 800 octets décompressés. C’est la première limite atteinte qui compte, et sur un site aux URL longues la limite de taille arrive généralement avant celle du nombre.
Au-delà de l’une ou l’autre, découpez le fichier et listez les morceaux dans un index de sitemaps : un petit fichier dans le même espace de noms dont les enfants sont des éléments <sitemap>, chacun avec un <loc>. Un index peut lister 50 000 sitemaps et est soumis aux mêmes 50 Mo. Soumettez l’index plutôt que chaque enfant, et référencez-le depuis robots.txt.
Pourquoi le bouton Récupérer a-t-il échoué sur mon sitemap ?
Presque toujours CORS. Votre navigateur n’autorisera pas cette page à lire une réponse d’un autre domaine à moins que ce domaine n’envoie un en-tête Access-Control-Allow-Origin, et peu de serveurs en envoient un pour sitemap.xml. C’est la politique de votre serveur, pas un défaut du fichier.
Nous ne contournons pas cela par un proxy, puisqu’un proxy ferait transiter votre URL et votre fichier par un serveur que nous exploitons. Le message d’échec vous donne une commande curl pour l’adresse saisie. Un sitemap gzippé doit être décompressé d’abord : curl -sL https://example.com/sitemap.xml.gz | gunzip.
Mon sitemap est-il envoyé quelque part ?
Non. L’analyseur et toutes les règles sont du JavaScript qui tourne dans cet onglet. Il n’y a pas de back-end à qui envoyer quoi que ce soit, ni d’analytics ayant accès à l’éditeur. Ouvrez votre panneau réseau et validez un fichier : les ressources de la page se chargent une fois, puis plus rien.
Un sitemap publié est public, mais le fichier que vous déboguez souvent ne l’est pas. Les sitemaps de préproduction listent des pages non lancées, des outils internes et des sites clients sous accord de confidentialité, et plusieurs des vérificateurs bien placés sur cette recherche tournent côté serveur. Votre saisie est conservée dans le localStorage de ce navigateur pour qu’un rafraîchissement ne la perde pas ; Effacer la supprime.