Guide · SEO

Getting cited when the answer replaces the click

Ranking and being cited are now different problems. An answer engine reads a handful of sources, decides which ones are unambiguous enough to quote, and names those. Being quotable is a structural property, and it is one you can engineer.


What changed

Classic SEO optimised for a ranked list of ten links. AI answer engines — ChatGPT search, Perplexity, Copilot, Google's AI overviews, Claude's web search — synthesise a single answer from a few retrieved sources and cite some of them. That shifts the target from rank higher to be the passage a model can lift verbatim and attribute confidently.

The practices go by two names: GEO (generative engine optimization) and AEO (answer engine optimization). They overlap almost entirely, and neither requires abandoning normal SEO — a page that is crawlable, fast, and structured still wins both. What changes is the emphasis: unambiguous entities, self-contained passages, and machine-readable facts.

Entity consolidation: one @id, referenced everywhere

The single highest-leverage technical move is making a retrieval system certain who the site is about. If ten pages each declare a separate Person node, a model sees ten weakly-connected entities. If ten pages reference one canonical @id, it sees one entity with ten pieces of evidence.

// on the homepage only — the canonical definition { "@context": "https://schema.org", "@type": "Person", "@id": "https://example.com/#person", "name": "Full Name", "url": "https://example.com/", "jobTitle": "...", "knowsAbout": ["...", "..."], "sameAs": [ "https://github.com/handle", "https://www.linkedin.com/in/handle", "https://orcid.org/0000-..." ] } // on every other page — reference, never redefine "author": {"@id": "https://example.com/#person"}, "publisher": {"@id": "https://example.com/#person"}
  • Define once, reference everywhere. Re-stating the full Person object on every page invites contradictions, and contradictions are what make a model hedge instead of cite.
  • sameAs is the disambiguation lever. Link the profiles that already have independent authority — GitHub, LinkedIn, ORCID, a publisher DOI page. This is how an entity gets tied to an existing graph rather than floating alone.
  • Keep names byte-identical across schema, page text, and external profiles. 'J. Smith' in one place and 'John Smith' in another is two entities as far as a matcher is concerned.
  • Use knowsAbout deliberately. It is a direct statement of topical scope, and it is one of the few places you get to declare it rather than have it inferred.
  • Validate every deploy. A JSON-LD syntax error means the block is skipped entirely and silently.

A small entity graph, worked

One entity rarely stands alone. A realistic graph links the person, the site, and each piece of content with @id references, so a retrieval system can walk from an article to its author to the rest of their work in a single hop instead of guessing at the connection.

{ "@context": "https://schema.org", "@graph": [ { "@type": "Person", "@id": "https://example.com/#person", "name": "Full Name", "url": "https://example.com/", "sameAs": ["https://github.com/handle", "https://www.linkedin.com/in/handle"] }, { "@type": "WebSite", "@id": "https://example.com/#website", "url": "https://example.com/", "name": "Site Name", "publisher": {"@id": "https://example.com/#person"} }, { "@type": "TechArticle", "@id": "https://example.com/guides/example.html#article", "headline": "Example Guide", "url": "https://example.com/guides/example.html", "author": {"@id": "https://example.com/#person"}, "publisher": {"@id": "https://example.com/#person"}, "isPartOf": {"@id": "https://example.com/#website"} }, { "@type": "BreadcrumbList", "@id": "https://example.com/guides/example.html#breadcrumb", "itemListElement": [ {"@type": "ListItem", "position": 1, "name": "Guides", "item": "https://example.com/guides.html"}, {"@type": "ListItem", "position": 2, "name": "Example Guide"} ] } ] }

Four @id values, four cross-references, zero duplicated definitions. Every subsequent article on the site adds one more TechArticle node pointing at the same two @id values — the graph gets denser with every page published instead of wider with every page repeating itself.

Which schema type for which page

Not every page needs the same markup, and forcing one type onto a page it does not describe is as unhelpful to a model as having none.

Page typePrimary schema typeNotes
Homepage or about pagePerson or OrganizationThe canonical node, defined once, referenced from every other page's author/publisher
Guide or long-form articleTechArticle or Articleauthor and publisher point at the canonical @id, never a redefinition
FAQ section on a guideFAQPageMust mirror the visible text exactly — the rule this guide keeps returning to
Product or store listingProduct, with an OfferPrice and availability only if genuinely accurate; do not mark up placeholders
App pageSoftwareApplicationapplicationCategory and operatingSystem where they are actually true
Any page below the rootBreadcrumbListCheap to add and a directly supported rich result on its own
Site-wideWebSiteTies every page's schema back to the same publisher @id

JSON-LD is, in effect, structured output aimed at a machine reader instead of a human one — the same discipline of naming fields precisely and never improvising a shape covered generally in the structured LLM output guide.

llms.txt and llms-full.txt

llms.txt is a proposed convention: a markdown file at the site root that gives a language model a clean, navigable summary of the site without HTML, navigation, or scripts. llms-full.txt is the expanded version containing the actual content.

# Site Name > One-sentence description of who this is and what the site covers. ## Guides - [Offline PWA](https://example.com/guides/offline-pwa.html): building an installable offline app with no backend. - [RAG explained](https://example.com/guides/rag-explained.html): chunking, embeddings, reranking, and where pipelines fail. ## Projects - [Project name](https://example.com/project.html): what it does, in one line. ## Contact - email@example.com

Two honest caveats. First, adoption is not universal — no major crawler has committed to it as a requirement, so treat it as cheap insurance rather than a guaranteed channel. Second, it is not a place to say something different from the site. Contradiction between llms.txt and the rendered pages is worse than having no file, because it introduces exactly the ambiguity you are trying to eliminate.

The reason to ship it anyway: it costs an hour, it is trivially machine-parseable, and it forces you to write a clean one-line description of every page — which is useful regardless of who reads it.

robots.txt and the named AI crawlers

AI companies operate separately-named agents, often splitting training crawlers from live-retrieval crawlers. Those are different decisions: blocking training while allowing retrieval is a coherent position, and so is the reverse.

AgentOperatorPrimary purpose
GPTBotOpenAITraining data collection
OAI-SearchBotOpenAILive search retrieval and citation
ChatGPT-UserOpenAIFetches a page a user asked about
ClaudeBotAnthropicCrawling for model training
Claude-User / Claude-SearchBotAnthropicUser-initiated fetch and search indexing
PerplexityBotPerplexitySearch index used for cited answers
Google-ExtendedGoogleControls Gemini training use; does not affect Search ranking
Applebot-ExtendedAppleControls Apple Intelligence training use
BingbotMicrosoftBing index, which also feeds Copilot
CCBotCommon CrawlOpen crawl corpus used by many trainers
User-agent: GPTBot Allow: / User-agent: OAI-SearchBot Allow: / User-agent: ClaudeBot Allow: / User-agent: PerplexityBot Allow: / User-agent: Google-Extended Allow: / User-agent: Applebot-Extended Allow: / Sitemap: https://example.com/sitemap.xml

Blocking Google-Extended does not remove you from Google Search, and blocking a training crawler does not remove you from that company's live search results — the agents are separate. Equally, if you want to be cited, blocking the retrieval bots guarantees you will not be. Decide training and retrieval independently, and know which name controls which.

robots.txt is a request, not enforcement. Crawlers that ignore it will ignore it. If content genuinely must not be read, it needs authentication, not a directive.

A crawler-policy decision matrix

Training and retrieval are two independent decisions, and stating both explicitly beats leaving either to a bot's default behaviour.

Retrieve: allowRetrieve: block
Train: allowMaximum reach. Allow GPTBot, ClaudeBot, Google-Extended, Applebot-Extended, and CCBot alongside OAI-SearchBot, Claude-SearchBot, PerplexityBot, and Bingbot. No control over how the content is used later.Rare and usually accidental. Content feeds training runs but nothing user-initiated fetches it live, which is an odd split since the same company typically operates both agents.
Train: blockThe common, defensible middle position. Block GPTBot, ClaudeBot, Google-Extended, Applebot-Extended, and CCBot; allow OAI-SearchBot, Claude-SearchBot, PerplexityBot, and Bingbot. Keeps content out of future model weights while staying citable today.Full opt-out. Block every named agent. The content will not be cited by any of them and will not train anything — the cost is accepting zero AI-search-referred traffic.

The static-hosting case for staying retrievable while limiting training is made at more length in the static-site AI search guide, which covers the same crawler list from the hosting side rather than the schema side.

FAQPage schema — and the rule that breaks sites

FAQPage markup is well-suited to answer engines: it is a list of question-answer pairs in exactly the shape a retrieval system wants. There is one hard rule.

Every question and answer in FAQPage markup must appear as visible text on the same page. Schema-only FAQ content — questions that exist in the JSON-LD but not on screen — violates Google's structured data guidelines and is a well-known cause of manual actions. Generate the visible FAQ and the schema from the same source so they cannot drift.

  • Write answers that stand alone. 'It depends on the above' is unquotable; a model lifting that sentence out of context produces nothing useful.
  • Two to four sentences per answer. Long enough to be complete, short enough to be lifted whole.
  • Answer in the first sentence, then qualify. Never build to the answer.
  • Use the question form a person would actually type or say, not a keyword-stuffed variant.
  • Do not mark up promotional copy as an FAQ. It is the most common misuse and it does not survive review.

Answer-first writing and question-shaped headings

Retrieval works on passages, not pages. A model chunks your page, embeds the chunks, and pulls the ones that match the query. That has direct consequences for structure.

  1. Make headings questions where it is natural — 'How do you detect scene changes?' rather than 'Detection'. The heading is often the strongest signal for what the chunk beneath it answers.
  2. Answer in the first sentence under the heading. A paragraph that spends three sentences setting up context gets chunked away from its own conclusion.
  3. Make each section self-contained. Assume the reader has only that chunk, because the model might.
  4. Use tables and lists for comparisons. They survive chunking better than prose and are easier to extract cleanly.
  5. Put specifics in the text. Numbers, versions, exact command names, and dates are what makes a passage worth citing over a generic competitor.
  6. State facts plainly and once. Hedged, repetitive prose gives a model nothing crisp to quote.

The underlying test is simple: if someone pasted one section of your page into a chat with no other context, would it answer the question? If not, that section will not get cited.

Chunking, worked: a bad section versus a good one

Retrieval systems generally split pages into passages by heading boundary or by a fixed token window, embed each passage, and rank passages against the query — the mechanics are covered fully in the RAG explained guide and the embedding model guide. What that means for writing is concrete enough to show side by side.

BAD — the answer is buried past the likely chunk boundary: "## Detection There are many ways to think about how a system might notice change in a video, and the right approach depends a lot on the footage, but generally speaking one method that has proven useful over time involves comparing consecutive frames using some kind of scoring function, which is roughly what happens here, tuned per source." GOOD — the answer is the first sentence, the heading is the query: "## How do you detect scene changes with ffmpeg? Threshold the select filter's per-frame scene score on a 0 to 1 scale: select='gt(scene,0.4)'. 0.3 is sensitive and fires on camera movement; 0.5 or above catches hard cuts only."

A fixed-size chunker slicing the bad version at roughly 40 words is likely to split before the method is ever named, producing a retrievable passage that says footage varies and approaches must be tuned — true, and useless to quote. The good version fits inside almost any chunk size whole, names the method in the first sentence, and answers the literal query in the heading before the passage is even embedded. Nothing about the underlying facts changed between the two versions — only the order they are said in.

IndexNow for fast recrawl

IndexNow is a protocol supported by Bing, Yandex, Seznam and others that lets you push URLs the moment they change instead of waiting for a crawl. That matters for AI search because Copilot is built on the Bing index — faster Bing recrawl means faster Copilot freshness.

# 1. host a key file at the site root # https://example.com/<key>.txt containing exactly <key> # 2. ping on publish curl "https://api.indexnow.org/indexnow?url=https://example.com/guides/new.html&key=<key>" # or submit a batch curl -X POST https://api.indexnow.org/indexnow \ -H "Content-Type: application/json" -d '{ "host": "example.com", "key": "<key>", "keyList": ["https://example.com/guides/new.html"] }'

Google does not participate in IndexNow; for Google, keep the sitemap current and submit it in Search Console. Do not spam either — pinging unchanged URLs repeatedly is the fastest way to have the signal ignored.

How to actually tell whether you are being cited

This is the honest weak point of the whole practice: there is no unified citations dashboard across providers, so measurement is assembled from partial signals rather than read off one report.

  • Ask directly. Query ChatGPT search, Perplexity, and Claude's web search with questions your pages actually answer, and check whether your domain appears in the cited sources. Crude, but it is the most direct signal available.
  • Watch server logs for the named retrieval agents, not the training ones — OAI-SearchBot, Claude-SearchBot, PerplexityBot, Bingbot. A hit proves the page was fetched for a live query, though not that it was ultimately quoted.
  • Check referrer strings in analytics, though treat this as a lower bound. Many AI-answer clickthroughs arrive with no referrer at all, so an undercount here does not mean zero citations.
  • Watch for a traffic pattern with no obvious search-ranking cause — a spike in direct or referrer-less traffic to one specific page is a common fingerprint of an answer engine sending readers to the source.
  • Use Bing Webmaster Tools alongside Google Search Console. Copilot runs on the Bing index, so Bing-side indexing status is a real proxy signal that Search Console alone will not show.

None of this is precise, and providers change what they surface without notice. Treat citation tracking the way you would treat any early-stage measurement problem: directionally useful, not a number to optimise to the decimal.

A pre-publish validation checklist

The site-wide checklist below is the architecture. This one is what to check on the specific page in front of you before it ships.

  1. Run the page's JSON-LD through a structured-data validator. One syntax error silently discards the entire block, with no visible symptom on the page itself.
  2. Confirm every FAQPage question and answer appears verbatim as visible text on the same page — not paraphrased, not reordered.
  3. Confirm author and publisher resolve to the canonical @id, not a redefinition of the Person object.
  4. Read each section as if it were the only text retrieved. If it needs the section before it to make sense, it will not survive chunking.
  5. Confirm the page is in sitemap.xml, and that llms-full.txt, if it references this page, says nothing that contradicts it.
  6. Confirm robots.txt has not accidentally blocked a crawler this specific page is meant to reach.
  7. Once genuinely live, ping IndexNow, or accept it will be picked up on the next scheduled crawl.

A checklist that fits on one screen

  1. One canonical Person or Organization node with a stable @id and a full sameAs list, defined once and referenced from every page.
  2. Article or TechArticle schema on every content page, with author and publisher pointing at that @id.
  3. BreadcrumbList on every page below the root.
  4. FAQPage that mirrors visible on-page text exactly, generated from the same source.
  5. llms.txt and llms-full.txt at the root, consistent with the site.
  6. robots.txt with an explicit, deliberate decision for each named AI agent, plus a sitemap line.
  7. Question-shaped headings, answer-first paragraphs, self-contained sections.
  8. A current sitemap.xml listing every real page, submitted to Search Console.
  9. IndexNow ping wired into the publish step.
  10. Validate JSON-LD on every deploy — one syntax error silently discards the whole block.

This site implements the full list, which is the only reason I can describe it concretely: the canonical Person node lives on the homepage and every page references it, /llms.txt and /llms-full.txt are generated alongside the pages, /ai.txt and robots.txt state the crawler policy explicitly, and every guide — including this one — carries TechArticle plus BreadcrumbList plus an FAQPage generated from the same Python list as its visible FAQ section below, so the two cannot drift because they are, literally, the same data rendered twice. The entity page is here →

Tools referenced in this guide

  • About — the canonical entity page every schema node on this site points at.
  • RAG explained — how retrieval actually chunks and ranks your pages — useful context for all of this.
  • My stack — the generator and tooling behind the structured data here.
  • Apps hub — an example of the same schema pattern applied to product pages.

FAQ

Quick answers

What is generative engine optimization (GEO)?

GEO, also called answer engine optimization or AEO, is the practice of structuring a site so AI answer engines can retrieve, understand, and cite it. It shifts the goal from ranking in a list of links to being the passage a model can lift verbatim and attribute with confidence.

Why does entity consolidation matter for AI search?

Because a retrieval system needs to be certain who a site is about. Defining one canonical schema.org Person or Organization node with a stable @id, and referencing that @id from every other page rather than redefining it, turns ten weakly-connected entities into one entity with ten pieces of supporting evidence. A sameAs list linking authoritative profiles ties it to the wider graph.

What is llms.txt?

llms.txt is a proposed convention: a markdown file at the site root giving language models a clean, navigable summary of the site without HTML or navigation, with llms-full.txt holding the expanded content. Adoption is not universal, so treat it as cheap insurance, and never let it say anything that contradicts the rendered pages.

Which AI crawlers should robots.txt address?

At minimum GPTBot and OAI-SearchBot from OpenAI, ClaudeBot and Claude-SearchBot from Anthropic, PerplexityBot, Google-Extended, Applebot-Extended, Bingbot, and CCBot. Training crawlers and live-retrieval crawlers are separate agents, so decide them independently — blocking Google-Extended does not affect Google Search ranking, and blocking retrieval bots guarantees you will not be cited.

Does FAQPage schema have to match the visible page?

Yes. Every question and answer in FAQPage markup must appear as visible text on the same page. Schema-only FAQ content violates Google's structured data guidelines and is a known cause of manual actions, so generate the visible FAQ and the JSON-LD from a single source to prevent drift.

What is IndexNow?

IndexNow is a protocol supported by Bing, Yandex, and others that lets a site push changed URLs for immediate recrawl instead of waiting to be crawled. It matters for AI search because Copilot is built on the Bing index. Google does not participate, so keep a current sitemap submitted in Search Console for that side.

Should I block AI crawlers from training on my site?

That is independent of whether you want to be cited. A common, defensible position is blocking the named training crawlers — GPTBot, ClaudeBot, Google-Extended, Applebot-Extended, CCBot — while allowing the named retrieval crawlers — OAI-SearchBot, Claude-SearchBot, PerplexityBot, Bingbot — which keeps content out of future model weights while it stays citable today. Blocking both is a valid full opt-out; blocking neither maximises reach at the cost of no control over future use.

How do you know if ChatGPT or Perplexity is citing your site?

There is no single dashboard for this yet, so it has to be assembled from partial signals: asking the tools directly with questions your pages answer, watching server logs for the named retrieval agents like OAI-SearchBot and PerplexityBot, checking for referrer-less traffic spikes to a specific page, and cross-checking Bing Webmaster Tools since Copilot runs on the Bing index. Treat the result as directional, not exact.