Duvlify
GitHubGet started
GuidesAuthoring pages

Authoring pages

A file's location sets its URL. Add frontmatter, list the page in a navigation group, and use drafts and noindex to control what ships.

Pages live in content/. Files under src/ build the publishing interface. You normally do not need to touch them when you write pages.

Add a page

Create the file where you want it served

content/guides/my-new-page.mdx becomes /guides/my-new-page. There is no path field. The file location sets the URL.

Give it frontmatter

YAML
---
title: My new page
description: A clear sentence explaining what the reader will learn.
---

title and description are the only required fields. See Frontmatter for the other fields.

Write it in Markdown

Write ordinary Markdown: headings, lists, tables, links, and code fences. Use the component vocabulary when prose is not enough.

List it in a navigation group

The id is the path under content/ without the extension:

src/docs.config.ts
{ label: 'Publishing', folder: true, pages: ['guides/content', 'guides/my-new-page'] }

The position of the id in that array sets the sidebar order. The sidebar label, icon, and badge come from the page’s own frontmatter. Nothing is repeated here.

An optional homepage

A documentation homepage is a normal page that lives outside the navigation tree. Use it for a hub page with cards that should open at / but should never occupy a row in the sidebar.

Create the page, usually content/index.mdx. Then set both values:

src/docs.config.ts
export const site = {
  // …
  home: '/',
  homePageId: 'index',
} as const;

The page stays public and indexable. It appears in the sitemap, the Markdown corpus, search results, and the logo link. It is excluded only from the sidebar, category selection, and the previous/next sequence.

A page named as the homepage must not also appear in navigation. The build refuses that case, because the page would then be reachable at two URLs. Leave homePageId undefined for a docs-first site whose logo should link to its first page.

Drafts

draft: true is the switch for a page you are still writing. The file stays in content/. It stays in version control. It stays listed in src/docs.config.ts. Nothing about it reaches the built site.

OutputA draft page
HTML routenot generated
<page>.mdnot generated
Share cardnot generated
Sidebar, breadcrumb, previous/nextabsent
Search indexabsent
sitemap.xml, updates.xmlabsent
llms.txt, llms-full.txtabsent

Leave the page listed in the navigation config while it is a draft. Navigation resolution skips it silently. It also drops any group, or tab, that this leaves empty. Publishing then takes one line of frontmatter, with no second edit to remember.

The build still fails on a page id that names no file at all. The two cases look identical from the outside, but the build treats them as opposites on purpose. A draft is a decision. A missing file is a typo. Without this rule, a typo could silently remove a sidebar entry, and a reader would find it instead of CI.

noindex is a different switch

A draft does not exist on the site. A noindex page exists. It is routed, it is linked from the sidebar, and it is served as Markdown. It is kept out of every surface that broadcasts a page: search results, the sitemap, the updates feed, and the llms.txt corpus. The reasoning is that a page withheld from search should not be handed to a model either.

Use noindex for a page that must be reachable but must not be found.

Reusable snippets

Astro’s MDX already has a module system. Shared content is an ordinary import rather than a Duvlify-specific registry.

MDX
import AuthenticationNote from '../snippets/AuthenticationNote.mdx';

<AuthenticationNote audience="server SDKs" />

Keep snippets near the content that owns them. Pass variation through props. Avoid importing a whole page as a snippet. Normal imports stay portable, and they fail at build time when a path is wrong.

Content for humans, content for agents

<Visibility> is the only component whose HTML output and Markdown output intentionally differ.

MDX
<Visibility for="humans">Click **Create account** in the dashboard.</Visibility>
<Visibility for="agents">Call `POST /v1/accounts`.</Visibility>

The first block appears on the website. The second block appears in the page’s .md route and every agent surface derived from it. From one file, a procedure can describe its clicks for a reader and its calls for a model.

What a page’s Markdown twin contains

Every page is also served at <page>.md. Those exact bytes serve three roles at once: that route, the fetch tool’s payload, and the text the semantic indexer embeds. Anything repeated there is repeated three times, and it lands in the first chunk of the page.

The header carries title, description, canonical, and updated. The body carries the title as its # heading, then the content. Nothing else repeats: the description is not repeated as an opening paragraph, and the URL is not repeated as a Source: line.

There is one exception, which is why this page states it rather than leaving it unsaid. Inside llms-full.txt the header is off, so the Source: line reappears. It is the only thing that carries a page’s URL in that file, and that file’s own preamble tells the reader to cite the HTML URL.

Preview and validate

Terminal
npm run dev
npm run build

The development server updates as content changes. The production build validates content, TypeScript, MDX syntax, and every generated route.

What the build refuses

These are errors, not warnings. A bad edit cannot reach production.

SituationMessage
Missing or too-short title / descriptionNames the file and the field.
Sidebar lists a page id with no fileNames the id and the group that references it.
A published page appears in no sidebar groupLists every orphan and suggests draft: true.
Configured homepage is missing or listed in navigationNames the homepage id and explains the rule.
A tab declares no pages at allNames the tab. A tab whose pages are all drafts is dropped silently instead.
Every page in the navigation is a draftExplains that at least one page has to be published.
An unknown component tagNames the tag, e.g. Expected component 'Callot' to be defined.

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.