Making Web Pages Survive RAG Chunking: Writing Strategies for AI Citations

AI search cites at the passage level — rewrite pages around chunking logic to win citations.
AI search systems like Perplexity and ChatGPT Search don't read whole pages; they split them into passage chunks via a RAG pipeline and hand only a few to the model, so citations are won or lost at the passage level. The article offers a Python script to simulate heading-aware chunking and a set of static checks to find weak passages, plus fixes like self-contained answers, query-style headings, comparison tables, and JSON-LD — validated against the visible page.
When Perplexity, ChatGPT Search, or Google's AI features answer a question, they don't read your page the way a human does. The retrieval pipeline splits the page into passages, ranks those passages against the query, then hands a handful of them to the model. The model writes its answer based on those passages.
This means the unit that wins or loses a citation is the passage, not the whole page. A page may be excellent overall, but if its best answer is split across three chunks, or begins with "as mentioned above, it...", it can still lose the citation.
Nobody outside these companies knows exactly how they chunk. But the general mechanics are well understood from open-source RAG systems. You can simulate them well enough to find weak passages.
How a Typical Retriever Sees Your Page
A common pipeline runs like this:
- Crawl the HTML, strip navigation and boilerplate.
- Split by structure (headings), then by size (a few hundred tokens), with some overlap.
- Embed each chunk, and usually also score it with keyword search (BM25).
- Rerank the top candidates against the query.
- Pass the top few chunks plus their source URLs to the model.

Steps 2 and 4 are where page structure matters. A chunk that contains a heading phrased like a question, immediately followed by a direct answer, is both easy to match and easy to cite.
What is RAG? RAG (Retrieval-Augmented Generation) is the dominant architecture for LLM applications: before the model generates an answer, a retriever fetches relevant text snippets from an external corpus, then feeds those snippets to the model as context. It solves the problem of models whose parametric knowledge is outdated or cannot cover private content. A typical RAG system has two main parts — the retrieval side (crawl, chunk, embed, index, rerank) and the generation side (assemble context, write the answer). Products like Perplexity and ChatGPT Search are essentially RAG systems pointed at the public web, so whether a page gets cited depends on how "easy to handle" it is at every stage of the retrieval side, and chunking and reranking are precisely the stages where page structure most often costs you points.
The division of labor between BM25 and vector embeddings. Vector embeddings map text to high-dimensional vectors and excel at catching matches that are semantically similar but use different words (e.g., "cost" and "pricing"); BM25 is a classic term-frequency–inverse-document-frequency keyword scoring algorithm that excels at hitting exact original words, proper nouns, and numbers. The two are complementary, which is why many retrieval systems run both paths in parallel and merge the results — what's called "hybrid retrieval." The implication for content creators is direct: including both semantic expressions a searcher might use ("how much," "price") and exact terms ("Starter plan," "$49") in the heading and first sentence gives you signal on both recall paths, which is more reliable than relying on just one.
What is reranking? The first stage of retrieval usually prioritizes speed and broad recall, pulling back dozens or hundreds of candidate chunks; reranking is the second stage, using a slower but more precise model (commonly a cross-encoder) to evaluate query–passage relevance pairwise, then selecting the top few to hand to the generation model. A cross-encoder sees the full text of both the query and the passage at once, so it is extremely sensitive to the internal structure of a passage: a chunk that is semantically complete on its own and answers the question in its first sentence will tend to score much higher than one that requires context to be understood. This also explains why, at the reranking stage, backward references ("as mentioned earlier") and vague headings are especially lethal — they cause the chunk to lose key information when evaluated in isolation.
Simulating the Chunking Process
The following code splits a page at H2/H3 boundaries, then limits chunk size — roughly what many heading-aware splitters do:
import re, requests
from bs4 import BeautifulSoup
MAX_WORDS = 220
def chunks(url: str) -> list[dict]:
soup = BeautifulSoup(requests.get(url, timeout=20).text, "html.parser")
for t in soup(["script", "style", "nav", "footer", "header", "aside", "noscript"]):
t.decompose()
main = soup.find("main") or soup.find("article") or soup.body
out, heading, buf = [], "(intro)", []
def flush():
words = " ".join(buf).split()
for i in range(0, len(words), MAX_WORDS):
out.append({"heading": heading, "text": " ".join(words[i:i + MAX_WORDS])})
buf.clear()
for el in main.find_all(["h1", "h2", "h3", "p", "li", "td"]):
if el.name in ("h1", "h2", "h3"):
flush()
heading = el.get_text(" ", strip=True)
else:
buf.append(el.get_text(" ", strip=True))
flush()
return out
Print the chunks for one of your key pages and read them in isolation, as if each chunk were the only content the model receives. This exercise alone is often enough to be eye-opening.
Static Checks on Chunks
Now automate the reading process. Each of the following checks flags a passage that could lose to a cleaner one:
DANGLING = re.compile(r"^(this|that|these|those|it|they|as (mentioned|noted|discussed)|see above|the above)\b", re.I)
VAGUE_HEADINGS = {"overview", "introduction", "details", "more", "getting started", "our approach", "why us"}
def lint(c: dict) -> list[str]:
issues = []
words = c["text"].split()
first = " ".join(words[:40])
if DANGLING.match(c["text"]):
issues.append("starts with a backward reference; the chunk can't stand alone")
if c["heading"].lower().strip(" :") in VAGUE_HEADINGS:
issues.append(f"vague heading '{c['heading']}' carries no query signal")
if len(words) < 40:
issues.append("very short; may not contain a complete answer")
if not re.search(r"\d", c["text"]):
issues.append("no concrete numbers")
if c["heading"].endswith("?") and len(first.split(".")[0].split()) > 35:
issues.append("question heading, but the first sentence is too long; the answer may be buried")
return issues
These are heuristics. "No numbers" is not a flaw in every chunk. Use this output as a reading list, not a score.
Fix Patterns
Self-contained answers. Repeat the subject instead of using pronouns. The first sentence should stand on its own even if quoted in isolation.
- ## Pricing
- Plans start at $49 and include everything above.
+ ## How much does Acme Analytics cost?
+ Acme Analytics costs $49 per month for the Starter plan, up to 5 seats,
+ and $199 per month for the Growth plan, up to 25 seats. Both include unlimited events.
Answer first, then elaborate. Have each section start with one or two sentences that directly answer its heading, then expand. Retrievers rank a chunk by its full content, but the model cites what is easiest to extract.
Make headings into queries. "How does Acme compare to Mixpanel?" matches comparison queries. "What makes Acme different" does not.
Use tables for comparisons. Comparison tables chunk cleanly, and the model can quote them accurately. A comparison buried in four paragraphs of prose can do neither.
Keep sections at a reasonable size. A 1,500-word section under one H2 will be split down the middle. Break it up with H3s at natural boundaries.
Pin Down Facts with JSON-LD
Prose is ambiguous. Structured data states facts explicitly: who made it, what it costs, when it was updated. Search engines use it heavily, and pages with clean, consistent facts are less likely to be misquoted.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Acme Analytics",
"offers": [
{ "@type": "Offer", "name": "Starter", "price": "49", "priceCurrency": "USD" },
{ "@type": "Offer", "name": "Growth", "price": "199", "priceCurrency": "USD" }
]
}
</script>
Rules more important than which type you choose:
- Match the visible page. If the JSON-LD says $49 and the page says $59, you've created a contradiction the model has to resolve — and it may resolve it wrong.
- Validate it. Run it through the Schema.org validator or Google's Rich Results Test. Invalid JSON is silently ignored.
- Server-render it. JSON-LD injected by client-side JavaScript has the same visibility problems as any other client-rendered content.
Why structured data affects citations. JSON-LD is the serialization format recommended by the Schema.org vocabulary; it declares in machine-readable form the entities on a page and their properties: product name, offers, currency, update time, author, and so on. Search engines and some AI systems use it to build knowledge graphs and for disambiguation and fact verification. When a page's prose and its structured data disagree, the system has to judge between multiple candidate values, and it won't always choose correctly — which is exactly why the article stresses "match the visible page." Another common trap is choosing the wrong type: labeling a software product as Product rather than SoftwareApplication, or writing FAQ content as Article, prevents properties from being parsed correctly. The validation step cannot be skipped, because syntactically broken JSON-LD is silently discarded by parsers, leaving no visible anomaly on the page while all the structured benefits drop to zero.
What About llms.txt?
llms.txt is a proposed convention: place a Markdown file at the site root that summarizes the site and links to LLM-friendly versions of key pages. It's cheap to add and can help tools that read it, such as some coding assistants and documentation-handling agents. Google has said it doesn't use it for search, and no major AI search engine has confirmed relying on it for ranking or citation. Add it if it's easy, but don't treat it as a substitute for the passage-level work above.
Measuring Whether It Works
Structural rewriting is a hypothesis. Before rewriting, record a baseline for the page, ship the changes, and over the following weeks watch the citation rate for the prompts that page targets. Rewrite one page cluster at a time so you can attribute the change.
The short version: write every section as if it were the only passage the model can see, because often it is.
One-line Summary
AI search cites at the passage level, so pages must be rewritten around chunking logic to win citations.
Paragraph Summary
The article notes that AI search systems like Perplexity and ChatGPT Search don't read content page-by-page; instead, they split pages into passage chunks via a RAG retrieval pipeline, and after embedding, keyword scoring, and reranking, hand only a few chunks to the model to generate an answer — so the unit that wins or loses a citation is the passage, not the whole page. The author provides a Python script that simulates heading-aware chunking at H2/H3 boundaries and truncates by size, plus a set of static check rules (backward-reference openings, vague headings, chunks too short, missing numbers, etc.) to find weak passages. Fix directions include: making each chunk self-contained, answering first then elaborating, writing headings as query sentences, using tables for comparisons, and controlling section size. It also recommends pinning facts with JSON-LD (which must match the visible page, be validated, and be server-rendered), and keeping expectations about llms.txt realistic. Finally, use citation rate as the metric and validate effect page cluster by page cluster.
Related articles

JobRunner MCP Server: AI Agents Can Now Fix Failed Java Jobs
JobRunner launches an open-source MCP server enabling AI agents to view cluster status, diagnose failures, and recover Java jobs—incident response from any cloud coding environment or existing agent.

MLAI Digital Launches 9 Security Agents: MCP Server Safeguards the Full Lifecycle of Agentic AI
MLAI Digital launches Security MCP Server with nine built-in specialized security agents covering vulnerability discovery, attack simulation, prompt injection detection, API and infrastructure assessment, and accelerated remediation—embedding security validation across the full Agentic AI lifecycle from development to production.

Running Isaac Lab on H100 GPUs: Exploring Better Approaches
Exploring optimization methods for running Isaac Lab on NVIDIA H100 GPUs, including MIG configuration, mixed precision, TensorRT acceleration, and community experience to improve robotics simulation and reinforcement learning efficiency.