Duvlify
GitHubGet started
Building the siteCode and diagrams
Building the site

Code and diagrams

Fenced code blocks get syntax highlighting, a filename header and a copy button. Mermaid fences render real diagrams that follow the reader's colour mode.

Code blocks

Write a fenced code block. Do not use a component. Shiki highlights a fence, and the fence also gets the filename header and copy button. These come from src/lib/rehype-code-chrome.ts, which adds them.

MDX
```ts title="src/client.ts"
export const client = createClient({ token: process.env.API_TOKEN });
```

Which renders as:

src/client.ts
export const client = createClient({ token: process.env.API_TOKEN });

title="…" sets the filename in the header. Without it, the build uses the language name.

Both Shiki themes ship as CSS variables rather than inlined colours. Switching colour mode therefore needs no re-render, and a light theme’s inline styles never override dark mode.

Code groups

Group alternatives in a <CodeGroup>. Each fence’s title becomes its tab label. The group gets a single copy button that follows the selected tab.

MDX
<CodeGroup>
```bash title="npm"
npm install
```
```bash title="pnpm"
pnpm install
```
</CodeGroup>
npm
npm install
pnpm
pnpm install
yarn
yarn install

Every group that offers the same labels in the same order moves together. The build remembers the reader’s choice for the next page. Add dropdown when a horizontal language strip would be crowded. Add sync={false} to keep one group local.

Diagrams

A ```mermaid fence renders a real Mermaid diagram, with title="…" as its caption. The build downloads the library only on pages that contain a diagram. The drawing re-renders when the visitor changes colour mode.

MDX
```mermaid title="Publishing a change"
flowchart LR
  A["Author edits MDX"] --> B["Preview build"]
  B --> C["Production"]
```
flowchart LR A["Author edits MDX"] --> B["Preview build"] B --> C["Production"]
Publishing a change

For diagrams with actions={true}, interactive controls appear on hover or keyboard focus. These controls give directional movement, zoom, reset, and a fullscreen viewer with drag, mouse-wheel zoom, and keyboard navigation. Position the inline controls with placement="top-left", top-right, bottom-left, or bottom-right. Set actions={false} for a diagram that should stay static.

A <Mermaid> component also exists, for one-liners and for compatibility with content authored elsewhere. A fence is the better default choice.

Images

The build handles content images for you. It automatically adds loading="lazy" and decoding="async". It also adds intrinsic width and height, read from the file in public/, which is the file that matters. Without these values, the browser reserves no space, and the page jumps as each image arrives.

Wrap an image in <Frame> to give it a border and a caption:

MDX
<Frame caption="The rollout screen after a guard metric halts a stage.">
  ![A halted rollout](/screenshots/rollout-halted.png)
</Frame>

Icons

Every icon comes from Lucide through <Icon name="…" />. This applies in the navigation, in buttons, and in any icon slot on a Card or Tile. Never use a raw emoji or Unicode glyph (, , ). These render without error, but they look inconsistent beside Lucide’s icons, with a different weight, baseline, and style.

MDX
<Card title="Deploy everywhere">
  <Icon slot="icon" name="rocket" size={22} />

</Card>

name is a short alias, not the Lucide icon name directly. See the icons map in src/components/Icon.astro. If the icon you need is not there yet, import it from @lucide/astro and add one line to that map. Do not use a glyph as a shortcut instead.

Video embeds

The build rewrites a raw <iframe> in content, at build time, into the same bordered, edge-to-edge card that <Frame> produces. So an embed pasted from YouTube or Loom gets an intrinsic aspect ratio instead of the browser’s old default box. This runs as a remark plugin, before the HTML stage, so it also handles JSX in content authored for other platforms.

Use these docs with your AI tools

An AI agent can read this documentation directly. You do not need an account or an API key. Everything here is public and read-only.

Query these docs via MCP

Recommended

Add this server to Claude, Claude Code, Cursor, Mistral, or any tool that supports MCP. Your agent can then search Duvlify documentation and read it in full, instead of answering from memory.

https://duvlify.dev/mcp
  • searchFind the passages that answer a question.
  • fetchRead one page in full, as Markdown.
  • list_pagesSee every page in this documentation.

Query these docs over HTTP

The same tools also work as plain web requests. Use this for scripts, or for any tool that does not support MCP. There is one endpoint per tool. Arguments go in the query string, and the answer comes back as JSON.

https://duvlify.dev/api/docs/search?query=custom+domain

Read the OpenAPI description. It is built from the same definitions as the tools, so it always matches what the endpoints do.

Read these docs as Markdown

Add .md to any page URL to get its Markdown source. You can also send the headerAccept: text/markdown to the page URL itself.

To read the whole documentation in one file, open llms-full.txt. For a short index of every page, open llms.txt.