Generative engine optimization (GEO) is how you get found by the tools that now sit between people and the web. Think of ChatGPT, Perplexity, Claude and Google’s AI Overviews. Classic SEO optimizes for a ranked list of links that a human clicks. GEO optimizes for a model that reads a few sources, combines them, and cites the ones it trusted. The two overlap, because clean HTML and a sitemap help both. But a model would rather read text than render your app. So GEO leans on five concrete mechanisms. They are an llms.txt map, robots rules that let AI crawlers in, structured data, Markdown mirrors, and registries where agents and tools get indexed.
I ran that whole playbook against kaiord.com, my open-source, local-first training platform, in a single day. AI agents did the work, and I approved each pull request. This is a Day-1 report. I can show you what shipped and why, mechanism by mechanism, with a link to every artifact. I cannot yet show you what it earned. More on that at the end.
The five mechanisms, and the case for each
1. An llms.txt map (PR #955)
llms.txt is a small Markdown file at your site root that gives an LLM a curated map of your best pages and docs. Kaiord’s documentation already generated an excellent per-section llms.txt, but nothing at the root pointed agents to it. PR #955 added a root /llms.txt in the llmstxt.org format: product pages in both languages, the editor, the key docs as Markdown, the full corpus, GitHub, npm. It is the cheapest high-leverage file on the whole site.
2. Robots rules that let AI crawlers in (PR #955)
This is the embarrassing one. The robots.txt had carried Disallow: /editor/ since the original landing scaffold. So the application itself, the thing I most want cited, was invisible to every crawler and every answer engine. You can spend months on content and still forbid the machines from reading the best of it. The fix is one deleted line. Today kaiord.com/robots.txt reads:
User-agent: *
Allow: /
Sitemap: https://kaiord.com/sitemap.xml
# AI agents: a curated map of this site lives at https://kaiord.com/llms.txt
# Full docs corpus in markdown: https://kaiord.com/docs/llms-full.txt
The lesson to learn before anything else: audit what you already block before you optimize what you do not.
3. Structured data (PR #955, extended in #962)
A single-page app is a hard read for a crawler: an empty <div id="root"> and nothing else. So the editor shell got a descriptive title and an accurate meta description. More important, it got noscript content and a WebApplication JSON-LD block with a real featureList, so a crawler without JS sees real text instead of a blank frame. The landing gained a WebSite JSON-LD node that declares both languages, and a visible FAQ backed by FAQPage JSON-LD. A later PR kept that FAQ in sync with the on-screen copy. Structured data is how you hand a machine entities (“this is an application, these are its features”) instead of asking it to guess from your markup.
4. Markdown mirrors (PR #955 and #965)
Models read Markdown easily and render web apps only with effort. Kaiord’s docs already emit per-page .md mirrors and a single /docs/llms-full.txt that joins the whole corpus as plain text. A model can read the whole product in one fetch. PR #955 also turned the root sitemap into a proper index, so /, /es/, and /editor/ are listed at last. Then PR #965 added a set of long-tail converter pages (“convert FIT to ZWO”, “export Zwift workout to Garmin”) built on a repeatable GEO template. Each page has a direct-answer paragraph at the top and three concrete ways to do the task. Then a grounded “what survives the conversion” table and a couple of specific FAQs. That is content shaped like the questions people ask an assistant.
5. Registries (PR #956 and #962)
The last mechanism is to appear where agents and tools get catalogued. There were two moves. First, PR #956 fixed npm metadata. Every package’s homepage had linked back to its own npm page, so it pointed nobody at the docs. Now each points at its real docs page. The keyword sets were widened too, to the honest, high-signal terms people search (fit-parser, fit-to-tcx, zwift-workout, mcp-server). Second, PR #962 made visible that @kaiord/mcp is now published to the official MCP registry as io.github.pablo-albaladejo/kaiord and listed on Glama. It then put that where people evaluate the project: the landing’s developer section, its FAQ, and the README badges. If AI agents are a distribution channel, their registries are where you need to be listed.
The honest part: a gatekeeper said no
Same day, same program, one distribution move failed. I opened awesome-local-first PR #51 to add Kaiord to a curated list. That is the oldest and most human form of GEO: a citation on a page that LLMs already read. The maintainer closed it. I quote the reply exactly, because it is the most useful thing that happened all day:
Thanks — this is clearly a real, actively developed project. Passing for now though: at ~7 stars it’s below the traction bar for example apps, and the README doesn’t really substantiate the local-first architecture (offline behavior, on-device storage as the source of truth). Happy to revisit once either of those improves.
That is a fair no, and it teaches the thing the five mechanisms cannot. A technically correct PR (clean diff, accurate description, passing checks) is necessary but not sufficient. Human gatekeepers weigh substance and traction. Does the README prove the claim? Has the project earned enough signal to be worth a slot? You cannot automate your way past that. The reply even gives you the roadmap: prove the local-first architecture in the README, let the traction grow, and “revisit once either of those improves.” I would rather publish that no than pretend the day was perfect.
Too early to measure
This is where I stop short of the usual case-study move. It is Day 1. Everything above is verifiable: the PRs are merged, the files are live, the registry entry resolves. But I have no traffic, no citation counts and no ranking changes to report, because none exist yet. Anyone who shows you GEO “results” hours after shipping is selling something. The artifacts are the evidence. The outcomes are a measurement I will take later. Treat the checklist below as a set of mechanisms with proof, not as a promise of numbers.
A checklist you can repeat
If you want to run the same pass on your own project:
- Ship a root
llms.txtin the llmstxt.org format: a curated map to your best pages, docs, and full-text mirrors. - Audit
robots.txtfirst. Delete anyDisallowon pages you want cited. Point crawlers at your sitemap and llms.txt. - Add JSON-LD (
WebSite,WebApplicationorArticle,FAQPage) and realnoscripttext for anything JavaScript-heavy. Crawlers then get entities and content instead of an empty root div. - Mirror your docs as Markdown: per-page
.mdfiles plus one full-corpus file that a model can read in one fetch. - List your project in the registries your audience and their agents browse (the MCP registry, Glama, npm with honest homepages and keywords). Then show those listings on your own pages.
- For curated lists, prove the claim in your README and be honest about traction. Expect a bar on substance and traction, and treat a “revisit once it improves” as a roadmap, not as a rejection.
Related writing
- Evaluating LLM outputs in production — the eval harness behind the product this case study optimized.
- Shipping production software with AI agents — the workflow that shipped the whole GEO program in a day.
- Observability for LLM pipelines — instrumenting what your LLM systems actually do.
- A discovery file is a promise — the next layer after files: a real JSON API, MCP server and A2A agent behind the cards, and the checks left failing on purpose.