Sitemaps
Generate a standard sitemap.xml, semantic sitemap.md, and docs-scoped /docs/sitemap.md from
the same docs source. The XML file gives search crawlers a canonical URL list with freshness dates.
The Markdown file gives agents, contributors, and automation a readable map of the docs tree with
titles, descriptions, markdown URLs, last-updated dates, and related pages.
Quick Start
No config is required for the default runtime routes. Add sitemap only when you want to customize
the public base URL or route prefix:
import { defineDocs } from "@farming-labs/docs";
export default defineDocs({
entry: "docs",
sitemap: {
enabled: true,
baseUrl: "https://docs.example.com",
},
});That exposes these routes through the same docs server wrapper and public forwarding layer:
GET /sitemap.xmlGET /sitemap.mdGET /docs/sitemap.mdGET /.well-known/sitemap.mdGET /api/docs?format=sitemap-xmlGET /api/docs?format=sitemap-md
Next.js withDocs() adds the public rewrites automatically. TanStack Start, SvelteKit, Astro, and
Nuxt use the same generated public forwarder that already handles .md, llms.txt, .well-known,
and MCP routes.
Static Export
For static hosting, run the generator before your framework build:
pnpm exec docs sitemap generate
pnpm exec docs robots generateThe command writes:
.farming-labs/sitemap-manifest.jsonpublic/sitemap.xmlpublic/sitemap.mdpublic/docs/sitemap.mdpublic/.well-known/sitemap.md
SvelteKit uses static/ instead of public/.
docs sitemap generate is enough for static output. The --public flag is only an explicit spelling
for the default behavior. Use --manifest-only when a server-rendered app only needs the generated
manifest for accurate lastmod values and should keep the public route handled by the runtime.
Use docs robots generate --append if the project already has its own robots.txt and should keep
site-specific rules around the generated agent policy block.
For Astro, SvelteKit, Nuxt, and TanStack Start deployments that preload docs content, bundle the
manifest with the markdown files. Current init templates do this for you:
const contentFiles = import.meta.glob(
["/docs/**/*.{md,mdx}", "/AGENTS.md", "/AGENT.md", "/skill.md", "/.farming-labs/sitemap-manifest.json"],
{
query: "?raw",
import: "default",
eager: true,
},
) as Record<string, string>;That lets sitemap routes and per-page JSON-LD reuse the same stable lastmod dates in serverless
and static-friendly builds.
pnpm exec docs sitemap generate --manifest-onlyUse a custom config path for adapters whose config lives outside the project root:
pnpm exec docs sitemap generate --config src/lib/docs.config.tsCommon build scripts:
{
"scripts": {
"build": "docs sitemap generate --manifest-only && next build"
}
}For output: "export", drop --manifest-only so public/sitemap.xml, public/sitemap.md,
and public/docs/sitemap.md are written before next build.
When you dogfood unpublished workspace packages, you may need to call the built CLI file directly
after building the package. Published apps should use docs in package scripts or pnpm exec docs
from the terminal.
What lastmod Means
lastmod is the last known content update date for a page. It appears as <lastmod> in
sitemap.xml and as Last updated: in sitemap.md.
The CLI resolves dates in this order:
- the last git commit date for the page source file
- the filesystem modified date for the page source file
- no date, if neither source is available
At runtime the docs server applies an additional, higher-priority source before falling back to the filesystem:
- the date from the bundled
.farm-docs-last-modified.jsonmanifest (when present) - the last git commit date for the page source file (CLI generation)
- the filesystem modified date for the page source file
- no date, if none of the above is available
The bundled manifest is a versioned JSON file with the shape { version: 1, pages: Record<path, ISO-date> }. When the docs server finds this file in the content directory, it reads the manifest-provided date for each page first and only falls back to stat.mtime when no manifest entry exists. This makes the last-modified date stable across deployments and CI runs, because the bundled dates reflect the original Git history rather than the time files were copied during a redeploy.
For preloaded adapters (Astro, SvelteKit, Nuxt, TanStack Start), include the manifest alongside your content files so the runtime can read it in serverless and static-friendly environments:
const contentFiles = import.meta.glob(
["/docs/**/*.{md,mdx}", "/.farm-docs-last-modified.json"],
{ query: "?raw", import: "default", eager: true },
) as Record<string, string>;Git dates are preferred because they are stable across machines and CI runs. Filesystem dates are a fallback for new files, generated content, or projects that are not in a git checkout.
Freshness is useful because crawlers and agents can:
- detect pages that changed since the last crawl
- prioritize recently edited documentation
- avoid re-reading stable pages unnecessarily
- show contributors where docs may have gone stale
- reuse the same stable date in each page's JSON-LD
dateModifiedwhen the adapter preloads the generated manifest
If you do not want freshness dates, disable them per output:
export default defineDocs({
sitemap: {
xml: {
includeLastmod: false,
},
markdown: {
includeLastmod: false,
},
},
});Route Prefix
Use routePrefix when you want the sitemap routes under a custom prefix:
export default defineDocs({
sitemap: {
enabled: true,
routePrefix: "/docs-map",
baseUrl: "https://docs.example.com",
},
});This generates:
/docs-map/sitemap.xml/docs-map/sitemap.md/docs-map/.well-known/sitemap.md
The default /docs/sitemap.md alias is emitted only when routePrefix is empty. There is no
separate well-known route setting. The prefix applies to custom sitemap routes together so adapters
only need one forwarding rule.
Configuration Reference
export default defineDocs({
sitemap: {
enabled: true,
routePrefix: "",
baseUrl: "https://docs.example.com",
manifestPath: ".farming-labs/sitemap-manifest.json",
xml: {
enabled: true,
includeLastmod: true,
},
markdown: {
enabled: true,
includeDescriptions: true,
includeLastmod: true,
linkTarget: "both",
},
},
});| Option | Type | Default | Description |
|---|---|---|---|
sitemap | boolean | DocsSitemapConfig | true | Enable or configure sitemap routes. Set false to opt out. |
sitemap.enabled | boolean | true | Disable without removing the object. |
sitemap.routePrefix | string | "" | Prefix all public sitemap routes. |
sitemap.baseUrl | string | request origin at runtime, llmsTxt.baseUrl in the CLI | Absolute base URL for XML <loc> values. |
sitemap.manifestPath | string | .farming-labs/sitemap-manifest.json | Internal generated manifest path. |
sitemap.xml | boolean | SitemapXmlConfig | enabled | Enable or configure sitemap.xml. |
sitemap.xml.includeLastmod | boolean | true | Include <lastmod> when a page date is known. |
sitemap.markdown | boolean | SitemapMarkdownConfig | enabled | Enable or configure sitemap.md. |
sitemap.markdown.includeDescriptions | boolean | true | Include page descriptions in the Markdown sitemap. |
sitemap.markdown.includeLastmod | boolean | true | Include Last updated: lines when a page date is known. |
sitemap.markdown.linkTarget | "html" | "markdown" | "both" | "both" | Choose whether list items link to HTML pages, Markdown pages, or both. |
Output Examples
sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://docs.example.com/docs</loc>
<lastmod>2026-05-08</lastmod>
</url>
<url>
<loc>https://docs.example.com/docs/installation</loc>
<lastmod>2026-05-08</lastmod>
</url>
</urlset>sitemap.md
# Example Docs Sitemap
Base URL: https://docs.example.com
Docs entry: /docs
Generated: 2026-05-08
## Overview
- [Introduction](/docs)
Markdown: /docs.md
Description: Start here
Last updated: 2026-05-08
## Installation
- [Installation](/docs/installation)
Markdown: /docs/installation.md
Description: Install the docs framework
Last updated: 2026-05-08
Related: /docs/configurationManifest
{
"version": 1,
"generatedAt": "2026-05-08T00:00:00.000Z",
"baseUrl": "https://docs.example.com",
"entry": "docs",
"siteTitle": "Example Docs",
"pages": [
{
"url": "/docs/installation",
"absoluteUrl": "https://docs.example.com/docs/installation",
"markdownUrl": "/docs/installation.md",
"title": "Installation",
"description": "Install the docs framework",
"sourcePath": "docs/installation/page.mdx",
"lastmod": "2026-05-08",
"lastmodSource": "git",
"related": ["/docs/configuration"]
}
]
}Verification
Check the public routes:
curl "http://127.0.0.1:3000/sitemap.xml"
curl "http://127.0.0.1:3000/sitemap.md"
curl "http://127.0.0.1:3000/docs/sitemap.md"
curl "http://127.0.0.1:3000/.well-known/sitemap.md"
curl "http://127.0.0.1:3000/robots.txt"In CI, use --check to fail when generated static output is stale:
pnpm exec docs sitemap generate --checkUse the agent doctor when you want the sitemap to count toward the broader machine-facing surface:
pnpm exec docs doctor --agent
pnpm exec docs doctor --agent --url https://docs.example.comThe hosted doctor reads sitemap routes from /.well-known/agent.json, so custom routePrefix
values are checked through the configured routes instead of assuming root-level defaults.
Generate sitemap.xml, sitemap.md, and docs/sitemap.md for crawlers, agents, and static exports
How is this guide?