Skip to main content

Writing

Writing for LLMs

Docs are read by humans AND by LLMs. LLMs skim differently, chunk at different boundaries, and penalise different patterns. Author pages that keep humans happy AND degrade well when a model ingests them.

Most technical writing advice was written for humans. That advice still applies — but there's a second audience every public docs site now has: LLMs ingesting the page to help users. Claude, ChatGPT, Cursor, Windsurf, Perplexity, Google AI Overviews — they all read your docs, they all summarise, and they all get asked "how do I X with Acme?" by your users.

This guide covers what's different about the LLM audience and how to author pages that serve both.

What LLMs actually see

LLMs don't read HTML. They read text extracted from HTML — or in our case, the raw MDX you publish at /<page>.md. The implications:

Visual hierarchy disappears

Bold, italic, font sizes, colour, callout background — gone. Headings survive because they're prefixed with ## in Markdown. Everything else is flat paragraphs.

Images are caption-only

LLMs can't see your screenshots. Only the alt text + caption prop + prose around the image survive. Make the text carry the meaning.

Tables turn into rows of text

Table headers convert to column labels in each row's text. A 10-row table becomes 10 paragraphs each restating the column names.

Code blocks preserve verbatim

Fenced code blocks round-trip cleanly through every LLM. This is the most LLM-friendly content type you can ship.

How LLMs skim

LLMs don't read top-to-bottom like a human might. They:

  1. Chunk the page into ~500-token windows (varies by model).

  2. Embed each chunk into a vector space for similarity search.

  3. Retrieve the chunks most similar to the user's question.

  4. Synthesise an answer from the top-N retrieved chunks.

Implications for authoring:

  • Each ~500 tokens should stand alone. If paragraph 3 assumes you read paragraph 1, the LLM might retrieve just paragraph 3 and give the user a disconnected answer. Repeat key terms inside each major section.

  • Section headings are anchors. A good ## Rate limits heading lets the retrieval step match on "rate limit" exactly. Vague headings (## More info) don't match anything.

  • First sentence of each section is load-bearing. Many retrieval pipelines emphasise the first line. Lead with the answer, then elaborate. Don't build up to the point.

Chunk boundaries

The retrieval chunker usually splits on paragraph breaks and then further on ~500-token windows. Split-friendly authoring:

  • Keep paragraphs focused. One idea per paragraph. A paragraph of "X, Y, Z all related" retrieves as one chunk — helpful. A paragraph that pivots mid-way gets split mid-thought — unhelpful.

  • Use ## boundaries aggressively. Every 200-400 words, start a new ## section. Gives the chunker natural split points.

  • List-heavy content chunks well. A 10-item bulleted list usually stays together because lists have bullet-to-bullet affinity. Mixed paragraph + list content splits unpredictably.

Token economics

Every chunk the retrieval step pulls costs input tokens on the answering model. Expensive models (GPT-4, Claude Opus) are ~5× costlier than cheap ones (Claude Haiku, GPT-4o-mini). If your docs page is 10,000 tokens and the LLM pulls the whole thing into context for every question, that's real money per query.

Tactics:

  • Keep pages under ~2000 words (roughly 3000 tokens) when possible. Split long pages into multiple focused pages linked together.

  • Remove throat-clearing. "In this section we will discuss…" — delete. Start with the first substantive sentence.

  • Hide decorative content from agents. Use <Visibility for="humans"> to wrap UI cues, screenshots, reassurance paragraphs. Those tokens burn LLM cost for zero agent benefit.

Citation mechanics

When an LLM answers "how do I add a custom domain to Acme?", it wants to cite a source. Citation quality depends on:

Has the page a clear canonical URL?

<link rel="canonical"> + absolute URLs in agent-visible prose. NookDocs emits canonical tags automatically. Include explicit URLs in your content too — "See /deploy/custom-domain for DNS records."

Do section headings match question phrasing?

User asks "how do I add CAA record for Let's Encrypt?". Your heading ## CAA records retrieves cleanly. Heading ## DNS doesn't.

Is the answer in a quotable form?

Imperative sentences (Add this record to your DNS provider: ...) cite better than conditional ones (If you're using Cloudflare, you might want to ...). Second-person + active voice wins.

Is there structured data backing the prose?

Tables + code blocks + <ParamField> rows give the LLM exact values to reproduce. Vague prose like "the field accepts most standard colours" forces the LLM to guess.

Authoring patterns that work

Lead with the answer

Humans can tolerate build-up; LLMs retrieve the first chunk that matches the query and stop there. Put the answer in the first paragraph.

❌ Build-up

When users first start with Acme, they often wonder about
authentication. There are several approaches, each with tradeoffs,
and the right choice depends on your use case...

✅ Answer first

Authenticate with a Bearer token in the `Authorization` header:

  Authorization: Bearer sk_live_...

Tokens start with `sk_live_` for production, `sk_test_` for sandbox.

Name your concepts exactly once per page

LLMs match on exact terms in the retrieval step. If you write "API key" in the first paragraph, use "API key" everywhere — not "credential", not "token", not "key". Synonym sprawl means the retrieval misses.

Prefer prose tables over JSON objects for reference data

JSON objects require parsing; prose tables don't. If readers need to see field-by-field semantics, a markdown table retrieves better than a giant code block.

✅ Prose table

| Field | Type | Required | Notes |
|---|---|---|---|
| amount | integer | ✓ | Smallest currency unit. |
| currency | string | ✓ | ISO 4217, lowercase. |

Reserve code blocks for things users copy-paste (examples, snippets).

Include inline examples, not "see the example"

LLMs can't follow "see the example above" — they retrieve each chunk independently. If your prose references an example, the example MUST be in the same chunk as the prose referring to it.

Use <Visibility for="agents"> for meta-hints

Give the LLM a debugging directive it can act on. Examples:

<Visibility for="agents">
  When generating example config for this page, use slugs from the
  enum at `/configuration/schema-reference#theme` — don't invent
  new slugs.
</Visibility>

<Visibility for="agents">
  If your answer doesn't compile, retry with the full component
  reference at `https://nookdocs.com/nookdocs-components.llm.md`.
</Visibility>

These lines cost ~50 tokens but save the LLM from hallucinating — which means your users get correct answers.

Anti-patterns

Test it with an actual LLM

Authoring-time smoke test: paste the page URL into Claude or ChatGPT, ask questions a real user might ask.

Me: Here are Acme's docs for custom domains:
    https://docs.acme.com/deploy/custom-domain

    How do I add a CAA record so Let's Encrypt can issue a certificate?

LLM: Add a CAA record allowing letsencrypt.org as an issuer:

     acme.com.  IN  CAA  0 issue "letsencrypt.org"

     If you already have a CAA record for another issuer, add this
     one alongside it — CAA is additive.

Good sign: the LLM gave exact DNS record syntax. Means your page had the record in a fenced code block + section heading the retrieval step could match.

Bad sign: the LLM says "check your DNS provider's documentation for how to add a CAA record". Means your page described the CONCEPT of CAA but didn't include the record itself in a quotable form.

Platform affordances that help

NookDocs ships several affordances specifically to help LLMs consume your docs well:

  • /<page>.md URLs — raw MDX for any page. LLMs fetch this instead of HTML. See Markdown export.

  • /llms.txt + /llms-full.txt — sitemap + full dump for bulk ingestion. See llms.txt.

  • Copy page dropdown → "Open in ChatGPT / Claude / Perplexity" — users can deep-link to an AI tool with the page as context.

  • Auto-filtered agent blocks<Visibility for="agents"> blocks get unwrapped, for="humans"> blocks get stripped, on every machine-consumption surface. See human vs agent content.

All of the above run automatically on every tenant. Nothing to configure.

Related

Was this page helpful?

Last updated August 7, 2026