---
title: Configuration
description: Configure a Geistdocs site through local adapter files and package APIs
type: reference
summary: Configure site metadata, navigation, AI features, page actions, translations, and local adapters.
url: /docs/configuration
source: apps/template/content/docs/configuration.mdx
prerequisites:
  - /docs/getting-started
related:
  - /docs/migration
  - /docs/provider
  - /docs/env
  - /docs/guides/nested-navigation
  - /docs/versioned-docs
  - /docs/proxy
---

# Configuration

Configure Geistdocs through user-owned files in the generated project. Runtime features come from `@vercel/geistdocs`, while your local files control content, branding, routing adapters, and opt-in customization.

  Review this Geistdocs project configuration. Explain what each export in `geistdocs.tsx` does, identify which features are enabled, and suggest safe customizations that do not require editing package internals.

## Configuration files

| File | Purpose |
| --- | --- |
| `geistdocs.tsx` | Site-level settings, including logo, nav, GitHub repo, AI prompt, agent-readiness metadata, translations, and feature flags. |
| `next.config.ts` | Composes Fumadocs MDX, discovers App Router routes, and configures Next.js. |
| `source.config.ts` | Configures Fumadocs collections with the package-provided schema, Markdown plugins, and syntax theme. |
| `lib/geistdocs/config.tsx` | Converts `geistdocs.tsx` exports into the package config using `defineConfig`. |
| `source.config.ts` | Configures Fumadocs collections with package-safe schemas and Markdown processing. |
| `lib/geistdocs/source.ts` | Connects Fumadocs content collections to the package source adapter. |
| `components/geistdocs/mdx-components.tsx` | Adds or overrides MDX components. |
| `components/geistdocs/docs-layout.tsx` | Customizes the package-backed docs layout, including optional sidebar content. |
| `app/[lang]/docs/[[...slug]]/page.tsx` | Configures the package docs page renderer. |
| `app/[lang]/agents.md/route.ts` | Configures the agent-readiness entry point. |
| `app/[lang]/.well-known/mcp.json/route.ts` | Configures MCP server discovery. |
| `app/[lang]/llms.txt/route.ts` | Configures the all-docs Markdown endpoint. |
| `app/[lang]/llms.mdx/[[...slug]]/route.ts` | Configures single-page Markdown responses. |
| `proxy.ts` | Configures markdown negotiation, AI-agent rewrites, request tracking, and custom request hooks. |
| `content/docs/meta.json` | Controls sidebar order and section labels. |

Read [Configure sidebar navigation](/docs/guides/nested-navigation) for page ordering, titled separators, folder indexes, nested folders, and custom navigation links.

## `geistdocs.tsx`

The root `geistdocs.tsx` file is the primary configuration surface:

```tsx title="geistdocs.tsx"
import type { GeistdocsNavbarBrand } from "@vercel/geistdocs/config";

export const navbarBrand: GeistdocsNavbarBrand = "vercel";

export const Logo = () => <span>My Docs</span>;

export const github = {
  owner: "my-org",
  repo: "my-repo",
  branch: "main",
  editPath: "content/docs/{path}",
};

export const nav = [
  { label: "Docs", href: "/docs" },
  { label: "GitHub", href: "https://github.com/my-org/my-repo" },
];

export const title = "My Documentation";

export const prompt =
  "You are a helpful assistant that answers questions about My Documentation.";

export const suggestions = [
  "How do I get started?",
  "How do I deploy?",
];

export const agent = {
  product: {
    name: "My Product",
    description: "My Product helps teams build and deploy applications.",
  },
};

export const translations = {
  en: { displayName: "English" },
};

export const basePath: string | undefined = undefined;
export const siteId: string | undefined = undefined;
```

### Choose a navbar brand

Set the optional `navbarBrand` export to `"vercel"` (default) or `"labs"`:

```tsx title="geistdocs.tsx"
import type { GeistdocsNavbarBrand } from "@vercel/geistdocs/config";

export const navbarBrand: GeistdocsNavbarBrand = "labs";
```

The generated `lib/geistdocs/config.tsx` forwards this export to `defineConfig`. Existing sites can forward it in their adapter or set `navbarBrand: "labs"` directly in `defineConfig`. Private projects use the type from `@vercel/geistdocs-private/config`.

Labs links to [vercel.com/labs](https://vercel.com/labs) and adapts to light and dark themes. Brand selection preserves your project's `Logo`, `logoHref`, and navigation. It is independent of `navbarVariant`, which selects the `"oss"` (default) or `"standard"` layout; either supports both brands.

For new projects, both CLIs accept `--brand labs`; omitting it or using `--brand vercel` preserves the defaults. See [Create a Labs starter](/docs/getting-started#create-a-labs-starter).

### Group navbar links into a dropdown

A `nav` entry can group related links into a dropdown. Provide `items` instead of `href`. Each item needs a `label` and an `href`, and takes an optional `section`:

```tsx title="geistdocs.tsx"
export const nav = [
  { label: "Docs", href: "/docs" },
  {
    label: "Resources",
    items: [
      { label: "Cookbook", href: "/cookbook", section: "Learn" },
      { label: "Templates", href: "/templates", section: "Build" },
      { label: "GitHub", href: "https://github.com/my-org/my-repo" },
    ],
  },
];
```

On desktop, the group opens as a full-width panel below the navbar, following the vercel.com header pattern and matching the OSS products flyout. `section` values become column headings, preserving first-seen order; items without a `section` group into a column headed by the dropdown's own label. In the mobile menu, the group renders as a collapsible section. External URLs open in a new tab and show an external-link arrow.

## `defineConfig`

`lib/geistdocs/config.tsx` converts your root exports into a package config. Most projects only need the generated defaults:

```tsx title="lib/geistdocs/config.tsx"
import { defineConfig } from "@vercel/geistdocs/config";
import {
  agent,
  ai,
  basePath,
  github,
  Logo,
  nav,
  navbarBrand,
  prompt,
  siteId,
  suggestions,
  title,
  translations,
} from "@/geistdocs";

export const config = defineConfig({
  title,
  agent,
  defaultLanguage: "en",
  logo: <Logo />,
  github,
  nav,
  navbarBrand,
  basePath,
  siteId,
  translations,
  ai: {
    prompt,
    suggestions,
    ...ai,
  },
});
```

The navbar wordmark (`logo`) links to the site root by default. Set `logoHref` to keep visitors on the docs when the root redirects elsewhere — for example `logoHref: "/docs/getting-started"`. Like other navbar links, the href is prefixed with the active non-default language.

Advanced sites can add `content` and `versions` metadata so local adapters and custom UI share one configuration object:

```tsx title="lib/geistdocs/config.tsx"
export const config = defineConfig({
  // ...
  content: [
    { id: "docs", label: "Docs", dir: "content/docs", route: "/docs" },
    {
      id: "cookbook",
      label: "Cookbook",
      dir: "content/cookbook",
      route: "/cookbook",
    },
  ],
  versions: {
    current: "v6",
    items: [
      { id: "v6", label: "v6 latest" },
      { id: "v5", label: "v5", href: "https://v5.example.com/:path*" },
    ],
  },
});
```

The `ai` config accepts `retrieval: "mixedbread"` to use semantic documentation retrieval and `eveAgent` to answer Ask AI requests with a hosted eve framework agent:

```tsx title="geistdocs.tsx"
export const ai = {
  retrieval: "mixedbread",
  eveAgent: { url: "https://help-eve.example.dev" },
};
```

See [Ask AI](/docs/ask-ai) for the full eve agent mode behavior, including authentication. Read [Improve Ask AI answers with Mixedbread](/docs/mixedbread-retrieval) before enabling semantic retrieval.

Use [Versioned docs](/docs/versioned-docs) for versioned source setup and [Proxy and markdown routes](/docs/proxy) for route mappings.

## `source.config.ts`

Use the source-config-safe package export in `source.config.ts`. This file is evaluated by `fumadocs-mdx` during install and build, so it should avoid imports from runtime component entry points such as `@vercel/geistdocs/mdx`.

```ts title="source.config.ts"
import {
  defineGeistdocsSourceConfig,
  geistdocsFrontmatterSchema,
  geistdocsMetaSchema,
} from "@vercel/geistdocs/source-config";
import { defineDocs } from "fumadocs-mdx/config";

export const docs = defineDocs({
  dir: "content/docs",
  docs: {
    schema: geistdocsFrontmatterSchema,
    postprocess: {
      includeProcessedMarkdown: true,
    },
  },
  meta: {
    schema: geistdocsMetaSchema,
  },
});

export default defineGeistdocsSourceConfig();
```

To serve docs from the site root, use `route: "/"` in `content` and `baseUrl: "/"` in `createSource`. Root-mounted docs need explicit `markdownRoutes`; Geistdocs does not infer a broad `/*path` mapping because it can capture homepages and non-docs app routes.

## Next.js base paths

When Next.js mounts the application below a path, set the same value in `next.config.ts` and Geistdocs config:

```ts title="next.config.ts"
import { createGeistdocs } from "@vercel/geistdocs/next";
import type { NextConfig } from "next";

const withGeistdocs = createGeistdocs();

const config: NextConfig = {
  basePath: "/docs",
  cacheComponents: true,
  partialPrefetching: true,
};

export default withGeistdocs(config);
```

```tsx title="geistdocs.tsx"
export const basePath = "/docs";
```

Keep `content.route`, source `baseUrl`, navigation links, `getPageUrl`, search results, and proxy `markdownRoutes` app-local. Next.js applies the mount prefix to navigation. Geistdocs applies `basePath` separately to public page actions, Markdown metadata, generated Markdown and sitemap links, Ask AI citations, RSS discovery, and proxy rewrites.

For example, a root-mounted source with `baseUrl: "/"` still uses `page.url === "/guide"`. The public page is `/docs/guide`, and its Markdown URL is `/docs/guide.md`. The root page uses `/docs/index.md`.

## Page actions

Content actions appear in the **Copy page** menu beside the page title. **Scroll to top** and **Give feedback** remain below the table of contents so they stay available while you read. These actions are enabled by default and can be disabled through `defineConfig` in `lib/geistdocs/config.tsx`:

```tsx title="lib/geistdocs/config.tsx"
export const config = defineConfig({
  // ...
  pageActions: {
    editSource: false,
    scrollTop: true,
    copyPage: true,
    askAI: true,
    openInChat: false,
  },
  feedback: {
    enabled: false,
  },
});
```

`github.editPath` controls the file path used by the "Edit this page on GitHub" action. Use `{path}` where the page path should be inserted. For monorepos, include the app directory, such as `apps/docs/content/docs/{path}`.

## Page visibility

Use frontmatter to control whether a page appears in package-owned machine-readable surfaces:

```mdx title="content/docs/internal-note.mdx"
---
title: Internal note
description: Hidden from public indexes
internal: true
---
```

`internal: true` excludes a page from `llms.txt`, `sitemap.md`, search, and chat. `noindex: true` adds `robots: noindex` metadata and excludes the page from `sitemap.md`. Use `excludeFrom` for per-surface control:

```mdx
---
title: Draft guide
excludeFrom:
  - chat
  - search
---
```

Optional `tags`, `keywords`, and `canonical` frontmatter are also recognized by the package runtime.

### Gate page access per request

Use `createDocsPage({ canViewPage })` when route state, authentication, or another request-specific policy controls access to HTML pages. A denied page returns the standard Next.js not-found response. The same policy filters page metadata, breadcrumbs, previous and next links, and the tree returned by `getPageTree`.

Keep the page factory in a shared app-owned module so the page and layout use the same policy:

```tsx title="lib/geistdocs/docs-page.tsx"
import {
  createDocsPage,
  type DocsPageParams,
} from "@vercel/geistdocs/pages/docs";
import { config } from "./config";
import { geistdocsSource } from "./source";

interface PageParams extends DocsPageParams {
  audience: string;
}

export const docsPage = createDocsPage<PageParams>({
  config,
  source: geistdocsSource,
  canViewPage: (page, { params }) =>
    page.data.preview !== true || params.audience === "preview",
});
```

Use `docsPage.getPageTree(params)` for the layout tree instead of reading the source tree directly:

```tsx title="app/[lang]/[audience]/docs/layout.tsx"
import { DocsLayout } from "@/components/geistdocs/docs-layout";
import { docsPage } from "@/lib/geistdocs/docs-page";

const Layout = async ({
  children,
  params,
}: LayoutProps<"/[lang]/[audience]/docs">) => (
  <DocsLayout tree={await docsPage.getPageTree(await params)}>
    {children}
  </DocsLayout>
);

export default Layout;
```

`canViewPage` controls request-specific HTML access. Machine-readable indexes and Ask AI need one stable, cacheable public corpus. Define a synchronous `filterPublicPage` function. Pass it as `canViewPage` to `createDocsMarkdownRoute`, and as `filterPage` to `createLlmsRoute`, `createSitemapMarkdownRoute`, `createSearchRoute`, `createSearchExportRoute`, and `createChatRoute`:

```ts title="lib/geistdocs/page-access.ts"
export const filterPublicPage = (page: { data: { preview?: boolean } }) =>
  page.data.preview !== true;
```

Consumers choose the frontmatter field and access provider. Geistdocs does not require a specific authentication or feature flag system.

## Last modified dates

`sitemap.md` entries include a `Lastmod` field and RSS items carry a publish date when a page has a last-modified date. Geistdocs resolves the date for each page in this order:

1. The page's `lastModified` frontmatter value.
2. The file's most recent git commit date.
3. No date. The sitemap entry omits `Lastmod` and the RSS item falls back to the build date.

Set `lastModified` in frontmatter when a page needs a stable, editor-controlled date:

```mdx title="content/docs/example.mdx"
---
title: Example page
description: An example documentation page
lastModified: 2026-07-01
---
```

Git-based dates require full git history at build time. Vercel builds use a shallow clone by default, so pages that were not changed in the cloned history have no reachable commit date and their entries omit `Lastmod`. To make git-based dates work on Vercel, set the `VERCEL_DEEP_CLONE=true` environment variable on your project so builds clone the full history.

## User-owned adapters

Adapter files are safe to edit. Package updates do not overwrite them.

For example, add a custom MDX component:

```tsx title="components/geistdocs/mdx-components.tsx"
import { createMdxComponents } from "@vercel/geistdocs/mdx";
import type { MDXComponents } from "mdx/types";

const ProductCard = ({ name }: { name: string }) => <div>{name}</div>;

export const getMDXComponents = (components?: MDXComponents): MDXComponents =>
  createMdxComponents({
    ProductCard,
    ...components,
  });
```

Then use it in MDX:

```mdx
<ProductCard name="My product" />
```

## Docs page adapter

`createDocsPage` accepts hooks for route-specific behavior:

```tsx title="app/[lang]/docs/[[...slug]]/page.tsx"
const docsPage = createDocsPage({
  config,
  source: geistdocsSource,
  getPageUrl: ({ page }) => page.url,
  openGraph: {
    images: true,
  },
  metadata: ({ metadata }) => metadata,
  mdx: ({ link }) => getMDXComponents({ a: link }),
  tableOfContent: {
    header: <div className="mb-3">Above the table of contents</div>,
    footer: <div className="mt-3">Below the table of contents</div>,
  },
});
```

Use `getPageUrl` when the app-local route differs from the source URL, such as `/v5/docs`. Geistdocs keeps that value app-local for previous and next navigation, then derives the public action URL with `config.basePath`.

For unusual deployments, `getPublicPageUrl` can override the public HTML URL and `getMarkdownUrl` can override the Copy Page, View as Markdown, and metadata URL. Most sites should use the package defaults so these surfaces cannot drift apart.

Set `openGraph.images` to `true` only when your site includes the Geistdocs OG route. Use `metadata` to set canonical URLs, `robots`, or custom Open Graph fields for a specific route. The `tableOfContent.header` and `tableOfContent.footer` slots render immediately above and below the table of contents.

Section landing pages that use `title: Overview` produce a distinct document `<title>` automatically: Geistdocs substitutes the parent section label (for example, `Channels`) so browser tabs and search results stay meaningful instead of repeating "Overview". The visible page heading and breadcrumb are unchanged, and other pages keep their own title. Use the `metadata` callback to override the resolved title for a specific route.

Geistdocs wraps resolved documentation pages in a Next.js error boundary. Unexpected rendering failures show a generic **Try again** action while the navbar and sidebar remain available. Framework signals such as `notFound()` and `redirect()` continue to use their standard Next.js behavior.

Package-owned links fully prefetch documentation destinations. Pages returned by `generateStaticParams` therefore navigate directly to their complete static content without showing transient fallback UI.

## Package-owned runtime

Do not edit files inside `node_modules/@vercel/geistdocs`. Update the package instead:

```bash title="Terminal"
pnpm exec geistdocs update
```

If a release adds an optional prop or config field, opt into it by editing the relevant local adapter file. Existing adapters should keep compiling unless a release includes a breaking API change.
