Agentic SEO & llms.txt
An e-bike network rebuilt with a machine-first surface next to the human one, so assistants can read it and act on it.
- Architect, designer, engineer
- May–September 2026
- Solo
- llms.txt, .md mirrors, 13 agent endpoints
Impact
- 10,463First-time visitors, 4 May – 2 June 2026 (+2,682%)
- 13Public agent endpoints, keyless
- 39Hand-authored .md route mirrors

Premise
Discovery is moving from ten blue links to answers. When someone asks an assistant “where can I test-ride a Lemmo near Amsterdam Oost, and how do I cycle there?”, the winning site isn’t the one with the prettiest hero — it’s the one an agent can read and act on.
Mokumfiets is a Netherlands e-bike curation and recommended-dealer network. A React SPA is close to invisible to that class of reader: content lives behind hydration, and there is nothing to call once the agent has an intent. So I built a parallel, machine-first surface next to the human one and kept the two in lockstep.
The architecture at a glance
Three layers sit beside the React app, each answering a different question an agent asks: where do I look, what can I read, and what can I do. The fourth column is the part most sites skip, the governance that keeps the other three from drifting.
01
Discovery — where do I look
- /llms.txt directory
- /.well-known/ai-plugin.json
- /.well-known/mcp.json
- /openapi.yaml
02
Reading — what can I read
- 39 hand-authored .md route twins
- seo-render-markdown for the programmatic matrix
- Canonical link back to the HTML page
- AgentAffordance block in the HTML footer
03
Action — what can I do
- 13 keyless public endpoints
- agent-list-* / agent-*-availability
- agent-book-* with a 30-minute hold
- agent-find-dealer and route tools
04
Governance — how it stays true
- seo-agent-localization skill
- Triggers on any new user-facing feature
- Seven pillars enforced before ship
- Directory and .md updated in the same change
Discovery, reading, action and governance. The human SPA is unchanged; the machine surface runs beside it from the same content store.
Layer 1 — llms.txt, the front door
A single file at /llms.txt acts as the directory for every non-human reader, paired with /.well-known/ai-plugin.json, /.well-known/mcp.json and /openapi.yaml so ChatGPT, Claude and MCP-aware clients each find the format they expect.
- Agentic permissions in plain English
- Read every page and every .md sibling; act on a user’s behalf via the listed webhooks.
- A courtesy rate, not a silent block
- ≤ 5 requests/sec from a single source, stated openly rather than enforced by mystery.
- Explicitly welcomed crawlers
- GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot and anthropic-ai.
- A webhook registry
- Full request/response shape of every public endpoint, so an agent can plan a call without a schema fetch.
Layer 2 — .md mirrors
Every important route has a Markdown twin. Thirty-nine are hand-authored and committed — /index.md, brand pages, /repair.md, /buy.md, /dealer.md, /manifesto.md, plus per-SKU sheets under /buy/* and /c/*. Each carries a canonical link back to the HTML page, so citation flows to the real URL.
Hand-writing a twin for a programmatic SEO matrix of thousands of URLs isn’t feasible, so those are generated on demand: the seo-render-markdown edge function resolves /compare/{a}-vs-{b}, /brands/{b}/for/{use-case}, /problems/{problem}/{brand} and /use-cases/{case}/in/{city}, pulls the stored editorial content, and returns clean Markdown with a one-hour edge cache. Pages still being drafted say so honestly rather than emitting thin content. An AgentAffordance block in the page footer closes the loop on the human side: it tells any crawler landing on the HTML where the Markdown twin, the llms.txt directory and the booking webhook live.
---
canonical: https://mokumfiets.com/repair
locale: en
updated: 2026-05-04
---
# Bike repair in Amsterdam
Drop-off, pickup and workshop repair across Oost, Centrum and Zuid.
## Book
POST /functions/v1/agent-book-repair { slot_id, email }
→ 30-minute hold, resume link, status_urlLayer 3 — the skill that stops drift
The hard part isn’t building an agent surface; it’s stopping it from drifting the moment someone ships a new feature. The whole pipeline is encoded as an internal skill (seo-agent-localization) that triggers on any new user-facing feature and enforces seven pillars before it can ship.
The result: “agent-readable” is a property of the build process, not a project someone has to remember to re-run.
- 1 — SEO shell
- Canonical, single H1, JSON-LD, page primitives.
- 2 — Markdown mirror
- One .md twin per public route.
- 3 — Directory entries
- llms.txt and openapi.yaml updated in the same change.
- 4 — Agent-callable endpoints
- agent-list-*, agent-*-availability, agent-book-*.
- 5 — The hold pattern
- 30-minute email hold on reservations, resume link, expiry cron.
- 6 — Merchant feed variants
- Feed shapes generated alongside the page.
- 7 — Localization
- EN/NL/DE/FR/ES/DA with locale-prefixed URLs and full hreflang.
trigger: any new user-facing feature or public route
require:
- seo_shell: canonical + single H1 + JSON-LD
- markdown_mirror: one .md twin per public route
- directory: llms.txt + openapi.yaml updated in-change
- endpoints: agent-list-* / agent-*-availability / agent-book-*
- hold_pattern: 30-min email hold + resume link + expiry cron
- merchant_feed: feed variant generated alongside the page
- localization: en/nl/de/fr/es/da + hreflang
on_fail: block releaseLayer 4 — endpoints, including maps
The interesting bet was exposing actions, not just prose. Thirteen public functions are agent-facing today; all keyless, rate-limited and CORS-open: find and list dealers, book a test ride, book an at-home or in-shop repair, track a delivery, list tours and their availability, get a turn-by-turn cycling route, the maps-route / maps-semantic / maps-narrative core behind it, and mcp-fietsmaps exposing the same tools over MCP.
Maps is what made the network useful rather than merely listable. A route response returns polyline and GeoJSON, but also semantic_metadata, route_personality, ride_mood and a narrative_seed — enough for an assistant to say “twelve quiet minutes along the Amstel, one bridge, mostly brick” instead of dumping coordinates. Chained with agent-find-dealer, an assistant can answer the whole question — which shop, how far, how to ride there, and book the slot — without ever rendering the Mokumfiets UI.
- Every action returns a status_url
- The agent creates; the human owns, edits and cancels. No irreversible action hides inside a tool call.
- Dealer listings are unweighted
- No pay-to-rank ordering — which is exactly what makes an answer engine willing to quote them.
An assistant completes the job without rendering the Mokumfiets UI. Every action returns a status_url, so the human still owns the booking.
What happened
In the first full month after the agent surface and the programmatic matrix went live, 4 May to 2 June 2026, the site recorded 10,463 first-time visitors and 12,743 page views, against 376 first visits and 1,352 page views in the preceding 30 days: a 2,682% step-change in new visitors and 843% in views. Behaviour moved with it. Of 53,795 tracked events in the window, 3,639 were test-drive intents, up from 59 the month before.
The shape is a step-change rather than a curve. The month of May alone shows 7,738 views, up 546%, and 6,122 of them land on a single page: the free test-ride booking flow, which is also the page the agent endpoints resolve to. Discovery arrives in bursts because agents crawl and cite in bursts, but the floor it leaves behind is the point.
Before — 4 Apr – 3 MayAfter — 4 May – 2 Jun
- First visits
- +2,682%
- Before376After10,463
- Page views
- +843%
- Before1,352After12,743
- Test-drive intents
- ×62
- Before59After3,639
Each pair is scaled to its own row, so the before bars stay readable. First full month after launch.

4 May – 2 June 2026 vs 4 April – 3 May 2026: 10,463 first visits (+2,682%), 12,743 page views (+843%), 3,639 test-drive intents.

2–31 May 2026 vs 2 April – 1 May: 7,738 views (+546%), with 6,122 on the free test-ride booking flow the agent endpoints resolve to.
What I’d do differently
Three things would have been cheaper done first than retrofitted.
- Brand the endpoint host from day one
- llms.txt still publishes raw *.supabase.co function URLs while the rest of the docs moved to functions.mokumfiets.com; agents cache aggressively, so a host change is expensive after the fact.
- Instrument the agent surface separately
- Traffic spikes are visible, but per-endpoint attribution — which assistant, which tool, which conversion — should have been first-class from the start.
- Ship the .md twin in the same commit as the page
- Everything retrofitted cost more than the skill-enforced path did.
Next
Per-endpoint agent analytics, .md twins for the remaining long-tail matrix, and extending the maps tools with live dealer opening hours so an assistant can route to a shop that’s actually open.
Results
Outcomes
- 10,463 first-time visitors and 12,743 page views between 4 May and 2 June 2026, up 2,682% and 843% on the preceding 30 days
- Assistants can complete a booking end to end, so the network is reachable from surfaces Mokumfiets doesn’t own and doesn’t pay for
- The Markdown twins made every programmatic page citable on day one instead of waiting on render budget for JS-heavy pages
- MCP support means a Claude or ChatGPT user can attach Mokumfiets as a tool rather than a bookmark
Reflection
Learnings
- Agent legibility is an architecture decision, not a content-marketing one
- Exposing actions — not just prose — is what turns a listing into a destination an assistant can finish a task in
- Trust is bought with reversibility and fairness: a status_url on every action, and rankings nobody paid for
- Enforcing the pipeline in the build process is the only thing that keeps an agent surface true a month later