Je structured data is stuk en je validator zegt van niet
Search Console meldde één ontbrekend veld op onze eigen site. Bij het narekenen bleken er twee grotere fouten in te zitten waar geen enkele validator over klaagt.
Vorige week kwam er een melding binnen in Search Console over onze eigen site. Ontbrekend veld mainEntity op drie profielpagina’s. Kleine melding, drie pagina’s, opgelost in tien minuten.
Daarna ben ik gaan narekenen of de rest wel klopte. Dat had ik beter meteen kunnen doen. Er bleken twee andere fouten in te zitten, allebei groter dan de melding, en over geen van beide klaagt een validator.
Waarom je dit niet ziet
Een validator controleert of je JSON-LD geldig is. Of de haakjes kloppen, of de types bestaan, of de verplichte velden gevuld zijn. Dat is een syntaxcontrole, en die zegt niets over de vraag of er zinnige inhoud in staat.
Concreet: de Rich Results Test keurt een FAQ-antwoord goed waarin letterlijk [SEO Maastricht](/seo-maastricht) staat. Geldige JSON, gevuld tekstveld, groen vinkje. Dat de bezoeker daar rauwe markdown ziet en Google een kapotte zin, merkt geen enkele tool.
Zolang je pagina voor pagina in een validator plakt, blijf je dit soort dingen missen. Je test op de verkeerde vraag.
Fout 1: blokken die je met @id aan elkaar dénkt te knopen
Dit was de melding. Onze profielpagina’s zetten twee losse ld+json-blokken neer. Eén ProfilePage met de paginagegevens, en daarnaast een Person met naam, functie, foto en socials. Die twee waren verbonden via een @id-verwijzing, netjes volgens het boekje.
Google leest een ProfilePage alleen als profielpagina wanneer mainEntity naar de persoon wijst. Dat veld stond er niet. De Person stond er wel, twee regels verderop, met een @id dat naar dezelfde pagina wees. Voor mij duidelijk. Voor de parser niet.
Google merget nodes over meerdere scripts heen meestal wel op @id, maar bij de rich-result-controles is dat geen zekerheid. De veilige vorm is nesten:
{
"@type": "ProfilePage",
"@id": "https://synflow.nl/overons/chris#webpage",
"mainEntity": {
"@type": "Person",
"@id": "https://synflow.nl/overons/chris#person",
"name": "Chris Kettmann",
"jobTitle": "Oprichter"
}
}
Zelfde informatie, één blok, geen aanname over hoe slim de parser is. Het kost je niets om het zo te doen.
Fout 2: markdown in een veld dat geen markdown leest
Deze was groter. Onze landingspagina’s hebben allemaal een FAQ, en die vragen en antwoorden staan in de frontmatter van het contentbestand. In een aantal van die antwoorden staan interne links, want dat was het hele idee: vanuit een antwoord doorverwijzen naar de stadspagina of de dienst waar het over gaat.
Alleen gaat zo’n frontmatterveld nergens door een markdown-renderer. Het is een string, en die string werd rechtstreeks in de pagina gezet en rechtstreeks in de JSON-LD. Resultaat op beide plekken:
Voor stad-specifieke pagina's: [SEO Maastricht](/seo-maastricht),
[SEO Roermond](/seo-roermond) en meer.
De omvang bleek groter dan ik had gegokt. Over de hele site staan 389 FAQ-antwoorden. Daarvan hadden er 54 dit probleem, samen goed voor 88 links, verspreid over 30 pagina’s. Die 88 interne links bestonden dus niet. Ze stonden er als tekst, ze telden nergens mee, en de bezoeker las de haakjes gewoon mee.
Dat laatste is het pijnlijkste deel. Dit was geen technisch detail diep in de broncode. Dit stond zichtbaar in de tekst op dertig pagina’s, en niemand van ons had het gezien. Ook ik niet, en ik heb die pagina’s zelf opgeleverd.
Wat je in dat veld wél kwijt kunt: Google’s Answer accepteert een kleine set HTML. Daaronder <a>, <p>, <br>, lijsten en de gebruikelijke nadrukken. Alleen wil het daar volledige URL’s zien, geen relatieve paden. De oplossing was één helper die de tekst escapet en [tekst](/pad) omzet naar een echte link, met absolute URL voor de JSON-LD en relatief voor de pagina zelf.
Na de fix stonden er 89 zichtbare interne links op de site die er de dag ervoor niet waren. Dat is geen SEO-tactiek, dat is een bug die sinds mei meeliep.
Fout 3: schema dat iets anders beweert dan de pagina
Deze zat er bij ons niet in, maar ik kom hem bij vrijwel elke audit tegen, dus hij hoort in dit rijtje.
Een plugin of een template zet een FAQ-blok in de JSON-LD dat nergens op de pagina staat. Soms omdat iemand vond dat de vragen de opmaak verpestten, vaker omdat de plugin een apart invulveld heeft en de bouwer niet doorhad dat die inhoud ook zichtbaar moet zijn.
Google is daar expliciet over: de inhoud van je structured data moet voor de bezoeker zichtbaar zijn op diezelfde pagina. Verborgen FAQ-markup is geen slordigheid maar een overtreding van de richtlijnen, en het is precies het soort ding waar een handmatige maatregel op volgt.
Zelfde categorie: Product-markup met een prijs die niet meer klopt, AggregateRating op reviews die nergens staan, LocalBusiness met openingstijden uit 2023. Allemaal geldige JSON.
Hoe je het dan wel controleert
Niet met een validator, of in elk geval niet alleen. Wat werkt is één script over je gebouwde HTML dat elk ld+json-blok uit elke pagina trekt, parst, en op patronen zoekt. Bij ons was dat twintig regels Python en een minuut werk.
Waar je op zoekt:
- Markdown in tekstvelden. Zoek op
[...](...)en op losse sterretjes in elk stringveld. Als je content uit frontmatter of een CMS-veld komt, is de kans groot dat er ergens opmaak in zit die nooit gerenderd wordt. - Verplichte velden per type. Voor elk type dat je gebruikt: staat
mainEntityerin bijFAQPageenProfilePage, staatauthorendatePublishederin bijBlogPosting. - Verwijzingen die nergens heen gaan. Elk
@idwaarnaar je verwijst, moet ook echt als node bestaan. Een verwijzing naar een#organizationdie je op die pagina niet meestuurt, is een dood eind. - Schema tegen zichtbare tekst. Strip de scripts uit de HTML en controleer of de vragen uit je
FAQPageook in de body voorkomen. Dit vangt fout 3 in één keer voor je hele site.
De winst zit hem erin dat je dit over alle pagina’s tegelijk draait, en dat je het na elke build opnieuw kunt draaien. Wij hebben 114 pagina’s, dus handmatig controleren was nooit gebeurd. Nu is het een commando.
Waarom dit meer gaat tellen dan het lijkt
Als je nu denkt dat een kapotte FAQ-markup weinig kost, heb je voor Google grotendeels gelijk. Sinds 2023 toont Google FAQ-rich-results alleen nog bij overheids- en gezondheidssites. Voor de rest van ons levert dat blok in de SERP niets meer op.
Dat is precies waarom het de moeite is om er nu naar te kijken, en niet waarom het niet hoeft.
Die JSON-LD wordt namelijk nog steeds gelezen, alleen niet meer alleen door de zoekmachine. Een taalmodel dat jouw pagina samenvat, pakt liever een schoon machineleesbaar blok dan een pagina vol HTML. En anders dan Google’s parser, die jarenlang op rommelige input is getraind en veel stilzwijgend rechttrekt, neemt zo’n model letterlijk over wat er staat. Als jouw prijs, werkgebied of dienstomschrijving daar verkeerd in staat, is dat wat er wordt naverteld.
Rich results waren de zichtbare beloning voor nette markup. Die beloning is voor de meeste sites weggevallen, en het gevolg is dat bijna niemand zijn structured data nog controleert. Ondertussen is het publiek dat ernaar kijkt groter geworden.
Meer over hoe die verschuiving werkt staat in Waarom AEO het nieuwe SEO is, en het bestand dat daar vaak in één adem bij genoemd wordt behandel ik in Wat is llms.txt.
Waar ik zou beginnen
Draai dat script over je eigen site voordat je iets anders aanpakt. Niet omdat het spannend is, maar omdat je binnen een minuut weet of je hier een probleem hebt. Bij ons was het antwoord ja, op een site die we zelf gebouwd hebben en waarvan ik dacht dat hij op orde was.
Kom je er niet uit, of wil je dat iemand meekijkt: het hoort bij SEO & AEO, en wat er verder bij komt kijken staat op AEO bureau.