Ga naar inhoud
SEO 19 augustus 2026 · 9 min leestijd

Bijna geen enkele Astro sitemap heeft datums die kloppen

De standaard sitemap van Astro propt alles in één bestand en zet er geen datums in. Zo splits je hem per contenttype en geef je elke pagina een wijzigdatum die Google vertrouwt.

Sven Kersten

Sven Kersten

SEO Specialist & Online Marketeer

Een sitemap-indexbestand dat naar meerdere kleinere bestanden per contenttype verwijst, waarbij één groot cijfer laat zien dat op honderd procent van de pagina's een wijzigdatum staat tegenover dertien procent daarvoor

De officiële website van Astro somt 323 adressen op in zijn sitemap, en bij geen enkel daarvan staat wanneer de pagina voor het laatst veranderde. De documentatie-website van Astro doet het met bijna zesduizend adressen precies zo. De makers van het framework laten dus het enige veld weg waar Google echt naar kijkt, en vrijwel elke Astro-website die ik nameet doet dat na.

Dit stuk gaat over hoe je dat wel goed doet. Je sitemap splitsen per contenttype en elke pagina een wijzigdatum geven die klopt, allebei met de opties die al in het pakket zitten dat je waarschijnlijk gebruikt. Wil je eerst weten wat er precies in een sitemap hoort, begin daar. Ken je dat al, dan gaan we meteen de config in.

Wat de standaard sitemap van Astro wel en niet doet

Je voegt de sitemap toe met npx astro add sitemap, en vanaf dat moment rolt er bij elke build een sitemap-index.xml uit met precies één bestand eronder. Dat werkt, en voor een kleine website is het genoeg. Wat het niet doet is net zo belangrijk. Het splitst niet per contenttype, dus je blog, je podcast en je losse pagina’s belanden allemaal in dat ene bestand. En het zet uit zichzelf geen lastmod bij je pagina’s, de datum die vertelt wanneer een pagina voor het laatst veranderde.

De @astrojs/sitemap-integratie heeft dertien opties, en twee daarvan dichten precies die twee gaten. chunks splitst je sitemap per contenttype, en met serialize bepaal je zelf welke velden er per pagina in komen. De rest van dit stuk gaat over die twee.

De velden die er in 2026 nog toe doen

Voordat we gaan splitsen, eerst welke velden hun aandacht waard zijn. Een sitemap-regel kan vier dingen bevatten, en Google gebruikt er maar één van. Het adres telt, en de lastmod telt. De andere twee, priority en changefreq, negeert Google volledig. Dat staat letterlijk in de eigen documentatie, in de woorden “Google ignores <priority> and <changefreq> values”. De Astro-documentatie herhaalt het nog eens.

Toch zet vrijwel elke standaardconfiguratie ze er alsnog in. Bij ons stond op elk van de 409 adressen een priority en een changefreq, allebei zonder enige functie. Ze klinken nuttig en ze doen niets. Weghalen scheelt ballast en ruim een derde van de bestandsgrootte, en dat is precies wat we in serialize gaan doen.

De chunks-optie waarmee je per contenttype splitst

Sinds versie 3.7.0, uitgebracht in januari 2026, heeft @astrojs/sitemap een chunks-optie. Je geeft er een aantal functies aan mee, en elke functie schrijft zijn eigen sitemapbestand. De sleutel wordt de bestandsnaam. Dat vrijwel niemand deze optie kent heeft een simpele reden. De documentatie kwam pas vijf dagen na de functie zelf, met als toelichting dat de code per ongeluk zonder uitleg was samengevoegd.

Zo ziet het eruit als je op contenttype splitst:

// astro.config.mjs
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  integrations: [
    sitemap({
      chunks: {
        // elke sleutel wordt een apart bestand: sitemap-artikelen-0.xml
        artikelen: (item) => hoortBij(item.url) === 'artikelen' ? item : undefined,
        podcast:   (item) => hoortBij(item.url) === 'podcast' ? item : undefined,
      },
    }),
  ],
});

De callback ziet alleen item.url, dus de groep moet ergens vandaan komen. Bij ons staan artikelen op een root-adres, net als de losse pagina’s, dus splitsen op het adres kan niet. Wij bouwen daarom in de config één kaart van adres naar content collection, door de mappen onder src/content/ uit te lezen. Die kaart is het hoortBij uit het voorbeeld. Alles wat je niet in een eigen chunk vangt, komt automatisch in het restbestand terecht.

Waarom splitsen loont, ook ver onder de 50.000 adressen

Google staat één sitemap toe tot 50.000 adressen of 50 MB. Zit je daaronder, en dat geldt voor vrijwel elke website, dan hoef je technisch niets te splitsen. De echte reden is de dekking per bestand in Google Search Console, want Search Console rapporteert die dekking per ingediend bestand. Met één bestand van vierhonderd adressen zie je alleen dat er bijvoorbeeld twaalf pagina’s niet zijn opgenomen, zonder te weten welke. Met een bestand per type zie je meteen of het aan je artikelen ligt of aan je podcast. Yoast splitst in WordPress standaard, en om dezelfde reden zetten wij entryLimit op 1000 in plaats van de standaard 45.000: kleinere bestanden geven fijnmaziger cijfers.

Drie valkuilen die nergens gedocumenteerd staan

Ik heb de gecompileerde code van de integratie gelezen en daarna met opzet fout geconfigureerd om deze drie te bevestigen.

pages is een gereserveerde sleutel. De integratie gebruikt die naam zelf voor het restbestand. Noem je je eigen chunk pages, dan overschrijft het restbestand jouw definitie zonder een waarschuwing. Onze groep heet daarom paginas.

Een adres kan in twee bestanden tegelijk belanden. Elke callback loopt over de volledige lijst en sluit de andere niet uit. Laat je twee callbacks hetzelfde adres opeisen, dan staat het straks in beide bestanden. Een kaart waarin elk adres precies één content collection krijgt, lost dat vanzelf op.

Een lege chunk levert geen bestand op. Een callback die voor alles undefined teruggeeft schrijft niets weg en verschijnt niet in de index. Dat is prettig, want een collectie die tijdelijk leeg is geeft geen kapotte verwijzing.

Een lastmod die klopt in plaats van de builddatum

Nu het stuk dat niemand goed heeft opgelost. De splitsing is het makkelijke deel, een lastmod die klopt is het echte werk.

Waarom Google een datum die altijd meespringt gewoon negeert

Google gebruikt de lastmod alleen als die “consistently and verifiably” klopt, oftewel als de datum controleerbaar overeenkomt met de laatste inhoudelijke wijziging van de pagina. De meeste generators zetten er de builddatum in, het moment waarop je website voor het laatst is opgebouwd. Publiceer je één nieuw artikel, dan wordt alles opnieuw gebouwd en springt elke lastmod mee, ook op pagina’s waar in geen maanden iets veranderde. Dan geef je Google honderden datums die liegen. Dat is niet alleen nutteloos, het ondermijnt het vertrouwen in de datum voor je hele website.

De volgorde die wij aanhouden

De lastmod komt bij ons uit drie bronnen, in deze volgorde. Eerst updatedDate uit de frontmatter, als die er is. Anders pubDate. En als er geen datum in de frontmatter staat, de datum van de laatste git-commit op het bronbestand:

// per adres, in serialize()
item.lastmod =
  frontmatter.updatedDate ??
  frontmatter.pubDate ??
  gitDatum(bronbestand); // git log -1 --format=%cI -- <bestand>

delete item.priority;
delete item.changefreq;

Git als terugval, en niet de datum van het bestand zelf, omdat bestandstijden op een build-server bij elke run resetten. De commitdatum is de enige die de echte wijziging vasthoudt. Voor de losse .astro-pagina’s, die geen frontmatter met een datum hebben, is git de enige bron. Onthoud daarbij dat een commit ook een typfout kan zijn, terwijl Google de laatste inhoudelijke wijziging wil. Git is dus een benadering, en daarom houdt de frontmatter-datum voorrang.

De volgorde waarmee elke pagina zijn wijzigdatum krijgt: eerst de bijgewerkt-datum uit de frontmatter, anders de publicatiedatum, en als laatste terugval de datum van de laatste git-commit op het bronbestand.

Omdat serialize vóór chunks draait, rolt de juiste datum per bestand er vanzelf uit. De index stempelt elk onderliggend bestand met de nieuwste lastmod van de adressen erin, sinds versie 3.7.3. Een zoekmachine ziet zo dat je artikelen-bestand veranderde terwijl je podcast-bestand gelijk bleef.

Waarom je dit in Astro zelf moet bouwen

Je vraagt je misschien af waarom de integratie de frontmatter niet gewoon zelf leest. Het antwoord is een circulaire afhankelijkheid. Om astro:content te gebruiken heb je een geldige Astro-config nodig, en de sitemap-config zit juist in die config. Op het moment dat de integratie draait bestaan je content-collecties nog niet. Er liggen drie open voorstellen in de roadmap om integraties toegang tot content-collecties te geven, alle drie nog niet doorgevoerd. Tot die tijd leest iedereen die dit goed wil doen de frontmatter zelf van schijf, met readFileSync. Ook Joost de Valk, de oprichter van Yoast, lost het in zijn Astro-gids zo op.

Wat zeven Astro-websites in de praktijk laten zien

Ik heb zeven Astro-websites nagemeten. De lat ligt laag. De officiële website van Astro heeft 323 adressen zonder één datum. De documentatie-website propt 5.880 adressen in één bestand van ruim 9 MB, ook zonder één datum, terwijl Google bestanden onder de 10 MB adviseert. Van de zeven splitst er precies één zijn sitemap per contenttype, en die zet er dan alsnog builddatums in. De enige die de datums goed doet is de documentatie-website van Cloudflare, met een lastmod op elk van de 8.277 adressen, maar wel weer in één ongesplitst bestand.

Zeven gemeten Astro-websites naast elkaar, met per website het aantal adressen en het percentage daarvan met een echte wijzigdatum: astro.build en de documentatie op nul procent, één website die splitst maar builddatums zet, Cloudflare op honderd procent in één bestand, en onze eigen website op honderd procent verdeeld over aparte bestanden.

Met een lastmod per bestand uit de frontmatter, met git als terugval, zet je een aantoonbaar beter resultaat neer dan elke Astro-website die ik kon meten, de website van Astro zelf inbegrepen. Het verhaal is niet dat wij iets nieuws bouwden. De bouwstenen liggen er, bijna niemand gebruikt ze, en dit is hoe je het afmaakt.

Bij een website die met AI is gebouwd gaat dit vaker mis

Steeds meer mensen laten hun website met AI bouwen, en een deel daarvan draait onder de motorkap op Astro of iets vergelijkbaars. De sitemap die daar uitrolt ziet er keurig uit en geeft toch een verkeerd signaal, want ook die zet vrijwel altijd de builddatum neer. Bij een open framework heb je de config in handen en kun je het repareren. Bij een gesloten bouwer als Lovable zie je niet eens welke datum eruit komt, en aanpassen kan meestal niet. Het oog ziet een afgewerkte website, de zoekmachine ziet een fundament dat scheef staat.

Zo controleer je het op je eigen website

Ook zonder Astro kun je dit vandaag nakijken. Open je sitemap op je adres gevolgd door /sitemap.xml of /sitemap-index.xml. Springen alle datums bij elke build mee, ook op pagina’s waar je niks aan deed, dan is het de builddatum. Kijk daarna of er nog priority en changefreq in staan, want dan draag je ballast mee, en of er alleen adressen in staan die naar zichzelf canonicaliseren, dus geen doorstuur-adressen en geen pagina’s met een canonical naar een andere versie. Dien je sitemap tot slot in bij Google Search Console. Google Search Console rapporteert de dekking per ingediend bestand, dus met een sitemap per type zie je daar meteen waar pagina’s wegvallen. Draai je WordPress met Yoast, dan splitst die al netjes per type, en dat is precies de vergelijking die je in je hoofd wilt hebben.

Wil je het hele technische fundament onder je website laten nalopen, dan helpt mijn stuk over de rest van je technische fundament je verder, of vraag een website-analyse aan om te zien waar je nu staat. En loop je bij je eigen sitemap tegen iets aan wat ik hier niet behandel, stuur me een bericht, dan kijk ik met je mee.

Veelgestelde vragen

Technisch niet. Google staat één bestand toe tot 50.000 adressen, en daar zit vrijwel elke website ver onder. De winst van splitsen zit ergens anders: in Google Search Console zie je de dekking per ingediend bestand, dus met een apart bestand per contenttype zie je meteen of pagina's wegvallen bij je blog, je portfolio of je productpagina's.

Omdat de sitemap-integratie niet bij je content-collecties kan en dus niet weet wanneer een pagina inhoudelijk veranderde. De config van de integratie zit zelf in de Astro-config, en op dat moment bestaat astro:content nog niet. Er ligt een voorstel om dit op te lossen, nog niet doorgevoerd. Tot die tijd lees je de wijzigdatum zelf uit de frontmatter, met git als terugval.

Het is geen ramp, want Google negeert ze allebei. Het is wel ballast die een precisie suggereert die er niet is. Google zegt met zoveel woorden dat het de waarden van priority en changefreq negeert, en de Astro-documentatie herhaalt dat. Weghalen scheelt bovendien ruim een derde van de bestandsgrootte.

De integratie bouwt de sitemap uit de routes die tijdens de build al vaststaan. Zolang je pagina's vooraf worden gebouwd, en dat is de standaard, ook wanneer je op een edge-platform als Cloudflare Workers uitrolt, komt elke route in de sitemap. Alleen pagina's die puur op verzoek renderen zijn onzichtbaar voor de integratie, want tijdens de build bestaan ze nog niet als vast adres. Die zet je in customPages of je rendert ze vooraf.

Publiceer één nieuwe pagina en kijk daarna naar de datums van je andere pagina's. Springen die allemaal mee, ook pagina's waar je maanden niks aan deed, dan is het de builddatum en niet de echte wijzigdatum. Google gebruikt de datum alleen als die verifieerbaar klopt, dus een datum die bij elke build meespringt doet niets voor je.

Het probleem zit in vrijwel elke generator. Het verschil is dat je bij een gesloten bouwer als Lovable niet ziet welke datum eruit komt, en dat je hem meestal ook niet kunt aanpassen. Bij een open framework als Astro heb je de config in handen en kun je het wel goed doen.