Elke GEO-tool kan een zichtbaarheidsgrafiek tekenen. Dat is de makkelijke helft, en daar houden de meeste op.
De moeilijke helft is wat er daarna op je eigen site gebeurde. Je merk werd genoemd in een AI-antwoord. Prima. Kwam er iemand? Kocht er iemand iets? Een grafiek die omhoogloopt terwijl er verder niets beweegt is een grafiek, geen signaal.
BrandTrace is mijn poging tot die tweede helft.
Wat GEO eigenlijk is
Generative Engine Optimization is het ongemakkelijke kleine broertje van SEO. Mensen stellen hun vraag steeds vaker aan ChatGPT, Gemini, Claude, Perplexity of Grok in plaats van in een zoekbalk, en het antwoord noemt sommige merken wel en andere niet. Hoor jij bij die andere, dan krijg je geen troostprijs op positie 11. Je bent gewoon afwezig.
Daarmee worden drie vragen ineens geld waard:
- Hoe vaak noemt een AI-antwoord jou, bij de vragen die je klanten echt stellen?
- Wie wordt er in plaats van jou genoemd?
- Uit welke bronnen putten die antwoorden — en zijn jouw pagina's überhaupt leesbaar voor de crawlers erachter?
De keten sluiten
BrandTrace houdt vier signalen bij elkaar voor dezelfde URL, in hetzelfde tijdvenster: een AI-antwoord citeerde een bron, een AI-bot haalde die pagina op, er kwam een bezoeker binnen vanaf een AI-oppervlak, er volgde een conversie.
Laat ik precies zeggen wat die claim wel en niet is, want hier praat GEO-tooling zichzelf meestal voorbij. Vier signalen naast elkaar leggen is niet hetzelfde als aantonen dat het ene het andere veroorzaakte. Een uitkomst causaal toeschrijven aan een actie is een onopgelost probleem, en ik heb het niet opgelost. Wat ik wel doe: voorkomen dat je vier tools naast elkaar openzet om te schatten of de lijnen een beetje op elkaar lijken.
Daar volgen twee dingen uit.
Eigen data wint van gissen. Crawl-events, Search Console en SDK-sessies zijn harde feiten. Alles wat gemodelleerd of geschat is, staat als zodanig gelabeld. Kun je kiezen tussen afleiden en meten, dan meet je.
Een inzicht moet ergens toe leiden. Een gap-score die niet uitkomt bij een concrete pagina om aan te pakken is een half afgemaakte functie, geen dashboardwidget.
Crawl-logs als bron
Koppel een Cloudflare Worker, of upload je serverlogs, en je ziet precies welke AI-bots welke pagina's ophaalden, hoe vaak, en welke statuscode ze terugkregen. Dit vind ik het nuttigste deel, en het deel waar verder niemand zich druk om lijkt te maken.
Het legt pijnlijke dingen vroeg bloot. Blokkeert een regel in robots.txt stiekem GPTBot? Heeft een deploy een week lang 404's staan uitdelen aan een crawler? Is de pagina die je blijft optimaliseren er een die de crawler nooit heeft opgehaald?
Niets daarvan is afgeleid. Het is je eigen logregel.
De conversiekant
Een kleine pixel op je site stuurt pagina- en sessie-events naar een Go-ingestservice. Een doel benoemen is één regel in je eigen code:
brandtrace.track("signed_up")
Een standaardcatalogus plus soepele canonicalisatie maakt daar harde en zachte conversies van, terug te leiden naar de sessie, de landingspagina, en het AI-antwoord dat die pagina citeerde.
Antwoordklaar zijn, en één keuze die ik verdedig
Genoemd worden door een AI-engine is iets anders dan er leesbaar voor zijn.
Elke gecrawlde pagina krijgt een score op zeven signalen: crawler-toegang, gestructureerde data, leesbare inhoud, entiteitsduidelijkheid, koppenhiërarchie, llms.txt en citatiegereedheid. Het is met opzet het enige onderdeel zonder voorwaarden vooraf — geen Search Console-koppeling, geen SDK-installatie. Een gecrawlde site is genoeg.
Die score komt uit een kale, niet-gerenderde fetch en niet uit de headless render. Dat is de keuze die ik het hardst zou verdedigen. Wat een crawler te zien krijgt is wat de HTML zegt vóórdat er JavaScript draait. Tegen de tijd dat Chromium klaar is zijn een lege single-page-app-schil en een server-gerenderde pagina niet meer uit elkaar te houden — en op Chromium wacht een crawler niet.
Niet sturen op wat er nooit was
Een bronrapport dat citaten toont die het model uit zijn trainingsdata heeft verzonnen in plaats van van het web gehaald, is erger dan geen rapport: je gaat op pad om een redactie te benaderen die er nooit aan te pas is gekomen.
Elk bronrapport draagt daarom een grounding-status. Staat die op ungrounded, dan zijn de genoemde URL's gegenereerd en niet waargenomen, en is de juiste actie: geen. Dat staat er dan ook bij, in plaats van beide soorten citaten stilzwijgend als hetzelfde te presenteren.
Wat eronder zit
De API is FastAPI op een stapel domeinpakketten die niets van een webframework weten. De ingest is een aparte Go-service, omdat een pixelcollector en een rapportage-API het tegenovergestelde willen: de een moet niets weten en veel aankunnen, de ander moet alles weten en wordt zelden bevraagd.
Daartussen: Postgres, NATS JetStream, Valkey en een Playwright-crawler. Elke LLM-aanroep gaat via een interne gateway naar OpenRouter, getypeerd, afgerekend en per abonnementsvorm gerouteerd — wat als overhead klinkt tot je voor het eerst een maand aan modelkosten per klant moet verantwoorden.
De frontend is React en Vite, uitgeleverd als installeerbare PWA. Het draait op self-hosted k3s met SOPS-versleutelde secrets, traces naar SpanBarn, fouten naar BugBarn, productanalytics naar FunnelBarn. Dezelfde stack als alles wat ik verder draai.
Gebouwd voor bureaus
Het bureau is de klant waar ik op mik, dus is dat het uitgangspunt en geen enterprise-toevoeging achteraf: één creditpool over alle klantprojecten, meteen te herverdelen als een klant vertrekt; modelkeuze per project, zodat een klant die alleen ChatGPT wil niet voor vijf engines betaalt; gescheiden klantomgevingen met rollen per gebruiker; en een bulkexport van chats, zodat je een klant het ruwe bewijs als CSV kunt geven.
De grafiek is de makkelijke helft. Reken een GEO-tool af op de moeilijke — de mijne net zo goed.