Home /

docs

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:

docs.config.ts
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.xml
  • GET /sitemap.md
  • GET /docs/sitemap.md
  • GET /.well-known/sitemap.md
  • GET /api/docs?format=sitemap-xml
  • GET /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:

terminal
pnpm exec docs sitemap generate
pnpm exec docs robots generate

The command writes:

  • .farming-labs/sitemap-manifest.json
  • public/sitemap.xml
  • public/sitemap.md
  • public/docs/sitemap.md
  • public/.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:

docs.server.ts
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.

terminal
pnpm exec docs sitemap generate --manifest-only

Use a custom config path for adapters whose config lives outside the project root:

terminal
pnpm exec docs sitemap generate --config src/lib/docs.config.ts

Common build scripts:

package.json
{
  "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:

  1. the last git commit date for the page source file
  2. the filesystem modified date for the page source file
  3. no date, if neither source is available

At runtime the docs server applies an additional, higher-priority source before falling back to the filesystem:

  1. the date from the bundled .farm-docs-last-modified.json manifest (when present)
  2. the last git commit date for the page source file (CLI generation)
  3. the filesystem modified date for the page source file
  4. 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:

docs.server.ts
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 dateModified when the adapter preloads the generated manifest

If you do not want freshness dates, disable them per output:

docs.config.ts
export default defineDocs({
  sitemap: {
    xml: {
      includeLastmod: false,
    },
    markdown: {
      includeLastmod: false,
    },
  },
});

Route Prefix

Use routePrefix when you want the sitemap routes under a custom prefix:

docs.config.ts
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

docs.config.ts
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",
    },
  },
});
OptionTypeDefaultDescription
sitemapboolean | DocsSitemapConfigtrueEnable or configure sitemap routes. Set false to opt out.
sitemap.enabledbooleantrueDisable without removing the object.
sitemap.routePrefixstring""Prefix all public sitemap routes.
sitemap.baseUrlstringrequest origin at runtime, llmsTxt.baseUrl in the CLIAbsolute base URL for XML <loc> values.
sitemap.manifestPathstring.farming-labs/sitemap-manifest.jsonInternal generated manifest path.
sitemap.xmlboolean | SitemapXmlConfigenabledEnable or configure sitemap.xml.
sitemap.xml.includeLastmodbooleantrueInclude <lastmod> when a page date is known.
sitemap.markdownboolean | SitemapMarkdownConfigenabledEnable or configure sitemap.md.
sitemap.markdown.includeDescriptionsbooleantrueInclude page descriptions in the Markdown sitemap.
sitemap.markdown.includeLastmodbooleantrueInclude 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

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

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/configuration

Manifest

.farming-labs/sitemap-manifest.json
{
  "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:

terminal
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:

terminal
pnpm exec docs sitemap generate --check

Use the agent doctor when you want the sitemap to count toward the broader machine-facing surface:

terminal
pnpm exec docs doctor --agent
pnpm exec docs doctor --agent --url https://docs.example.com

The 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.

Customize
Live preview on these docs
Reset
Customize
Export Code
Preset
Explore more themes
Primary Color
Primary
Colors
Background
Foreground
Muted Text
Border
Card
Muted BG
Ring
Layout
SidebarBordered
TOC StyleDirectional (tree)
TOC DepthH2 + H3
Ask AI
ModeFloating
Floating StyleFull Modal
PositionBottom Right
Features