Skip to content

Markdown rendering

The markdown route serves your page’s source verbatim by default. On a Markdown site that is exactly right. On an MDX site it means the thing an AI agent fetches is full of import statements and component tags rather than prose.

renderMarkdown decides which of those the route serves.

This page in your editor:

<Tabs syncKey="pkg">
<TabItem label="npm">
```sh
npm install starlight-llm-actions
```
</TabItem>
<TabItem label="pnpm">
```sh
pnpm add starlight-llm-actions
```
</TabItem>
</Tabs>

Under the default 'raw', that is byte-for-byte what the .md route returns — component tags included. An agent has to guess what <Tabs> means.

Under 'simple', the same block arrives as ordinary Markdown:

* npm
```sh
npm install starlight-llm-actions
```
* pnpm
```sh
pnpm add starlight-llm-actions
```

Nothing is left for the reader to interpret. Starlight’s <Tabs>, <FileTree>, <Steps>, and Expressive Code blocks all flatten this way.

Mode Use it when
'raw' Your pages are plain Markdown, or you want the source — someone copying a page to file a docs PR needs the original, not a rendering of it.
'simple' Your pages use MDX components and the audience is AI agents or readers, not editors.
{ module } You need output neither mode produces — stripping sections, injecting frontmatter, rewriting links for an external index.

If you are unsure, 'raw' is the safer default: it has no dependencies and cannot fail.

The flattening pipeline is heavy, so the plugin does not bundle it:

Terminal window
npm install @astrojs/mdx unified rehype-parse rehype-remark remark-gfm remark-stringify hast-util-select unist-util-remove

Then set the mode:

starlightLlmActions({
renderMarkdown: 'simple',
})

Miss a dependency and the build stops at config time and prints the exact install command — it will not fail halfway through rendering your pages.

Framework components throw. 'simple' renders through an Astro container that registers the MDX renderer only, so a page using a React, Vue, Svelte, or Solid component cannot render. The plugin catches that per page, warns with the page name, and serves that page’s raw source instead. One such page will not fail your build or affect any other page.

Chrome leaks into the output. Unknown custom elements are unwrapped and their text kept. When an element renders pure chrome you do not want in the Markdown, mark it and the whole subtree is dropped:

<div data-mdast="ignore"></div>

Turning off the route makes the mode moot. With injectRoute: false there is no route to render, so the setting changes nothing — though 'simple' still expects its dependencies whenever it is set.

Rendering good Markdown only helps if something can find it. linkAlternate adds a discovery tag to every page:

starlightLlmActions({
linkAlternate: true,
})

Each page then carries a tag pointing at its own Markdown:

<link rel="alternate" type="text/markdown" href="/guides/example.md" />

Crawlers and agents can follow it without knowing your URL convention. Drafts and the 404 page are skipped, because the route produces no Markdown for them.

These docs run renderMarkdown: 'simple' and linkAlternate: true, so you can check both without installing anything. Open the Markdown for the install page and compare it against the <Tabs> source above — that is the flattening, running live. View the source of any page here and you will find its alternate tag in the <head>.