Validatore di sitemap XML

Tutte le regole del protocollo. Senza limiti di URL né registrazione.

Scaricato direttamente dall’origine. Molti server lo bloccano; se succede, incolla il contenuto.
Input
In attesaIncolla un documento per controllarlo. La convalida gira mentre scrivi.

Tutto viene eseguito in questa scheda. Nulla di ciò che incolli viene caricato, registrato o inviato da qualche parte. Apri il pannello di rete e verifica.

Incolla la tua sitemap qui sopra, oppure inserisci il suo indirizzo e premi Scarica, e ogni regola del protocollo sitemaps.org viene controllata in una volta: il numero di URL rispetto al limite di 50.000, la dimensione non compressa rispetto al limite di 50 MB, e il formato di ogni loc, lastmod, changefreq e priority. I file di indice hanno un proprio insieme di regole.

La maggior parte delle persone arriva da Search Console con un «Impossibile leggere la sitemap» o «La sitemap è leggibile ma contiene errori», e nessuno dei due indica una riga. Qui ogni rilievo cita il valore contestato, richiama la regola e dice che cosa scrivere al suo posto.

Due cose lo distinguono dal resto di questa ricerca. Nulla viene caricato, quindi una sitemap di staging che elenca pagine non ancora pubblicate resta sulla tua macchina. E i controlli che non hanno potuto essere eseguiti vengono segnalati come non eseguiti anziché come superati.

I due limiti rigidi

Una singola sitemap può elencare 50.000 URL e può pesare 50 MB non compressa, che il protocollo esprime come 52.428.800 byte. Entrambi vengono controllati, e la dimensione in byte è riportata che tu sia vicino al limite o no, perché è l’unico numero che non puoi valutare guardando il file.

Il gzip non regala margine: il limite si applica dopo la decompressione, quindi una sitemap.xml.gz da 3 MB che si espande in 61 MB lo supera. Un indice di sitemap porta gli stessi due limiti. Entrambi avvisano al novanta per cento, perché una sitemap costruita su un catalogo che cresce supera la soglia in un martedì qualunque.

  • 50.000 voci <url> per file di sitemap.
  • 52.428.800 byte non compressi, che tu usi gzip o meno.
  • 2.048 caratteri per valore di <loc>.
  • 50.000 voci <sitemap> per indice di sitemap.

Il namespace deve essere esatto

La radice è <urlset>, o <sitemapindex> per un indice, e deve dichiarare http://www.sitemaps.org/schemas/sitemap/0.9. Tre dettagli traggono in inganno: è http e non https anche su un sito https, non ha barra finale, e la versione è 0.9 e lo è sempre stata.

Sbaglialo e nulla protesterà ad alta voce. Il file resta ben formato e continua ad aprirsi in un browser. I motori di ricerca semplicemente non riconoscono elementi nel namespace sbagliato, quindi leggono la tua sitemap come un documento che non contiene nulla.

Mischiare figli <url> e <sitemap> è un errore, perché un file o è una sitemap o è un indice. La codifica dichiarata deve essere UTF-8. Degli elementi di estensione si verifica solo che abbiano un prefisso dichiarato; le regole di immagini, video e news non vengono convalidate qui.

lastmod, changefreq e priority

lastmod deve essere un W3C Datetime. AAAA-MM-GG è valido da solo; un timestamp completo è valido se porta un fuso, come 2026-03-14T09:30:00+00:00. Uno spazio al posto della T, un timestamp Unix, 14/03/2026, o un timestamp senza fuso sono tutti errori.

Gira un secondo controllo su lastmod che nessun altro validatore gratuito fa. Se più di quattro voci ne portano uno e tutti i valori sono identici, ricevi un avviso: il tuo generatore sta timbrando l’ora di build invece della data in cui il contenuto è cambiato. Google usa lastmod solo quando è costantemente e verificabilmente accurato, quindi quel motivo gli insegna a ignorare il campo.

changefreq accetta sette valori in minuscolo: always, hourly, daily, weekly, monthly, yearly, never. priority deve stare fra 0.0 e 1.0. Entrambi vengono inoltre segnalati come informazione quando sono presenti, perché Google dichiara nella propria documentazione di ignorarli.

Stesso host, stessa directory, e il recupero via URL

Una sitemap che sta a https://example.com/catalog/sitemap.xml può elencare URL sotto https://example.com/catalog/ e non può elencare URL sotto https://example.com/images/. Stesso host, stesso schema, nella propria directory o più in basso. Queste regole richiedono l’indirizzo a cui la sitemap sarà pubblicata, che il file non contiene.

È a questo che serve il campo URL, e puoi digitare un indirizzo senza scaricare nulla. Inseriscilo e si attivano tre controlli: un altro host è errore, uno schema diverso è errore, e un loc fuori dalla directory della sitemap è un avviso. Lascialo vuoto e tutti e tre compaiono come non eseguiti invece di passare in silenzio.

Scarica richiede il file dal tuo browser direttamente al tuo server, senza proxy in mezzo, quindi su molti hosting il CORS lo blocca. Quando accade ottieni il motivo e un comando curl da eseguire tu stesso.

Controllare una sitemap da codice

Vale la pena inserirlo in uno step di build, così una sitemap che ha superato il limite di 50.000 URL fa fallire il tuo deploy invece della tua indicizzazione. Ogni esempio conta le URL, misura la dimensione non compressa e controlla 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'

I flag di sicurezza non sono decorazione: Java, Python e C# risolvono tutti le entità esterne se non gli si dice altrimenti, e una sitemap è di solito un file che non hai scritto tu, arrivato via rete da un plugin. Il flag --nonet di xmllint fa lo stesso lavoro.

Domande frequenti

Search Console dice che la mia sitemap ha errori, ma questo strumento dice che va bene. Perché?

I due guardano cose diverse. Questa pagina controlla il file: struttura, namespace, limiti, date, e le regole su host e directory non appena fornisci l’indirizzo della sitemap. Search Console controlla anche ciò che solo un crawler può vedere, a partire dal fatto che il file fosse raggiungibile.

«Impossibile recuperare» di solito significa un 404, una catena di redirect, una regola di robots.txt che copre il percorso della sitemap, o una pagina di errore HTML servita con stato 200. Recuperare l’URL qui mostra il vero codice di stato. L’altra discrepanza frequente è che le URL sono valide ma le pagine dietro sono noindex o non esistono più, e questo è un problema di scansione.

Google usa changefreq e priority?

No. Google dichiara nella documentazione sulle sitemap di ignorarli entrambi. Questo strumento segnala la loro presenza come informazione anziché come errore, perché sono elementi leciti e non fanno danno.

In pratica: nessuna disposizione dei valori di priority farà scansionare una pagina prima di un’altra, e un changefreq impostato su hourly non alzerà la tua frequenza di scansione. Se mantieni codice che li calcola, quel codice non si sta guadagnando il posto. lastmod è l’eccezione, ed è l’unico elemento facoltativo che valga la pena curare.

In che formato deve essere lastmod?

W3C Datetime, un sottoinsieme di ISO 8601. La forma valida più semplice è una data secca, 2026-03-14. Se includi un orario devi includere un fuso: 2026-03-14T09:30:00Z e 2026-03-14T09:30:00+00:00 sono entrambi corretti.

Le forme rifiutate sono quelle che database e template producono naturalmente. Uno spazio al posto della T non è valido, un timestamp senza fuso non è valido, e nemmeno un numero epoch Unix o una data con il giorno prima come 14/03/2026. Se le date sbagliate vengono da un plugin e non dal tuo codice, il valore citato nell’errore di solito lascia capire quale.

Quante URL può contenere una sitemap?

50.000, e deve anche restare sotto i 52.428.800 byte non compressi. Conta quello che raggiungi per primo, e su un sito con URL lunghe il limite di dimensione arriva di solito prima di quello di conteggio.

Oltre l’uno o l’altro, spezza il file ed elenca le parti in un indice di sitemap: un file piccolo nello stesso namespace i cui figli sono elementi <sitemap>, ciascuno con un <loc>. Un indice può elencare 50.000 sitemap ed è soggetto agli stessi 50 MB. Invia l’indice invece di ogni singolo figlio, e richiamalo dal robots.txt.

Perché il pulsante Scarica ha fallito sulla mia sitemap?

Quasi sempre CORS. Il tuo browser non permette a questa pagina di leggere una risposta da un altro dominio a meno che quel dominio non invii un header Access-Control-Allow-Origin, e pochi server ne inviano uno per sitemap.xml. È la politica del tuo server, non un difetto del file.

Non lo aggiriamo con un proxy, perché un proxy porterebbe la tua URL e il tuo file attraverso un server gestito da noi. Il messaggio di errore ti fornisce un comando curl per l’indirizzo che hai inserito. Una sitemap compressa con gzip va prima decompressa: curl -sL https://example.com/sitemap.xml.gz | gunzip.

La mia sitemap viene caricata da qualche parte?

No. Il parser e ogni regola sono JavaScript che gira in questa scheda. Non c’è alcun back end a cui inviare qualcosa e nessun analytics con accesso all’editor. Apri il pannello di rete e convalida un file: le risorse della pagina si caricano una volta e poi più nulla.

Una sitemap pubblicata è pubblica, ma il file che stai debuggando spesso non lo è. Le sitemap di staging elencano pagine non lanciate, strumenti interni e siti di clienti coperti da riservatezza, e diversi dei controllori ben posizionati su questa ricerca girano lato server. Quello che scrivi resta nel localStorage di questo browser così un ricaricamento non lo perde; Svuota lo rimuove.

Strumenti correlati

Approfondimenti