---
title: "Quick Start"
description: "Getting started with Fumadocs involves bootstrapping a dedicated documentation workspace, configuring content collections, rendering routing hierarchies, and managing development environments. The ..."
last_updated: "2026-10-12T07:53:59.866213+00:00"
canonical_url: "https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/getting-started/quick-start"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/cli/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts)
- [packages/create-app/src/bin.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts)
- [packages/preview/src/cli/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/index.ts)
- [packages/python/pyproject.toml](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/python/pyproject.toml)
- [packages/python/fumapy/__init__.py](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/python/fumapy/__init__.py)
- [packages/openapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/openapi/src/server/index.tsx)
- [packages/mdx/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts)
- [packages/preview/src/pages/...slugs.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/preview/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/package.json)
- [packages/local-md/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/server.ts)
- [packages/create-app/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/package.json)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts)
- [packages/create-app/scripts/sync.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/scripts/sync.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/server/index.tsx)
- [packages/local-md/src/dev/react-client.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/react-client.ts)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/dynamic.ts)
- [packages/preview/fumadocs.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/fumadocs.config.ts)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/runtime.ts)
- [packages/cli/src/commands/export-epub.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts)
- [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)
- [packages/python/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/python/package.json)
- [packages/create-app/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/index.ts)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/package.json)
- [packages/cli/src/commands/add.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/add.ts)
- [packages/openapi/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/openapi/package.json)
- [packages/local-md/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/package.json)
- [packages/preview/src/cli/commands.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/commands.ts)
- [packages/asyncapi/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/package.json)
- [packages/python/fumapy/mksource/__init__.py](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/python/fumapy/mksource/__init__.py)
</details>

## Overview

Getting started with Fumadocs involves bootstrapping a dedicated documentation workspace, configuring content collections, rendering routing hierarchies, and managing development environments. The initialization workflow is powered by [Create App Generator](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/create-app-generator) and the [Fumadocs CLI](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/fumadocs-cli), which scaffold template applications, install dependencies, and configure linters across supported frameworks.

Sources: [packages/create-app/src/bin.ts:22-54](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts#L22-L54), [packages/cli/src/index.ts:17-27](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L17-L27)

## Bootstrapping with CLI tooling

### Scaffolding Workspaces and Command Options

Fumadocs provides dedicated command-line utilities to initialize documentation projects, manage configuration files, and interact with component registries. The primary package for scaffolding workspaces is `create-fumadocs-app`, which is versioned at `16.0.123` and bundles dependencies such as `@clack/prompts`, `tinyexec`, and `ts-morph`.

Sources: [packages/create-app/package.json:3-52](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/package.json#L3-L52)

Once inside a workspace, the `fumadocs` CLI (`@fumadocs/cli`) offers core routines for workspace initialization, layout customization, component downloads, and tree transformations. The CLI is powered by `commander` and `picocolors`.

Sources: [packages/cli/src/index.ts:4-20](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L4-L20)

| Command | Aliases | Options | Description |
| :--- | :--- | :--- | :--- |
| `fumadocs` (default) | none | `--config <string>` | Initializes a `./cli.json` configuration file if one does not already exist |
| `customise` | `customize` | `--dir <string>`, `--config <string>` | Simple way to customize layouts with Fumadocs UI |
| `add` | none | `--dir <string>`, `--config <string>` | Downloads and adds new components to your docs |
| `export epub` | none | `--framework <name>`, `--output <path>`, `--scaffold-only` | Exports documentation to EPUB format |
| `tree` | none | `--js`, `--no-root`, `--import-name <name>` | Generates JavaScript or MDX file trees from JSON or directory arguments |

Sources: [packages/cli/src/index.ts:17-83](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L17-L83)

> [!NOTE]
> The default action of the `fumadocs` binary checks for the existence of `./cli.json`. If it does not exist, it initializes the file and logs a success message; if it already exists, it emits a notice indicating that the config file is already present.
> Sources: [packages/cli/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L21-L27)

### Registry Connectors and Framework Targets

When executing commands that fetch remote or local components or layouts, the CLI resolves registry sources using `createClientFromDir`. Short-form directory shortcuts map specific keywords to designated endpoints.

```typescript
const dirShortcuts: Record<string, string> = {
  ':preview': 'https://preview.fumadocs.dev/registry',
  ':dev': 'http://localhost:3000/registry',
};
```
Sources: [packages/cli/src/index.ts:39-42](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L39-L42)

The `createClientFromDir` function inspects the directory path or shortcut. If the destination begins with `http://` or `https://`, it instantiates an `HttpRegistryConnector`; otherwise, it returns a `LocalRegistryConnector`.

```typescript
function createClientFromDir(dir = 'https://fumadocs.dev/registry') {
  if (dir in dirShortcuts) dir = dirShortcuts[dir];

  return dir.startsWith('http://') || dir.startsWith('https://')
    ? new HttpRegistryConnector(dir)
    : new LocalRegistryConnector(dir);
}
```
Sources: [packages/cli/src/index.ts:108-114](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L108-L114)

> [!WARNING]
> When executing `fumadocs export epub`, the `--framework` option is required. The supported framework names accepted by the command parser are `next`, `tanstack-start`, `react-router`, and `waku`.
> Sources: [packages/cli/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L57-L63)

## Configuring content collections and schemas

### Declaring Document Collections

Documentation sources are structured through collection configuration helpers provided by `@fumadocs/content`. The `docsCollection` function binds MDX documentation documents and metadata files into a unified store, accepting directory paths and custom validation schemas for both types of content.

```typescript
export interface DocsCollectionConfig<
  FrontmatterSchema extends StandardSchemaV1 | undefined,
  MetaSchema extends StandardSchemaV1 | undefined,
> {
  dir: string;
  docs: Omit<DocsMDXCollectionConfig<FrontmatterSchema>, 'dir'>;
  meta: Omit<DataCollectionConfig<MetaSchema>, 'dir'>;
}

export function docsCollection<
  FrontmatterSchema extends StandardSchemaV1 | typeof pageSchema = typeof pageSchema,
  MetaSchema extends StandardSchemaV1 | typeof metaSchema = typeof metaSchema,
>(config: DocsCollectionConfig<FrontmatterSchema, MetaSchema>) {
  return {
    index: new DocsCollection(),
    meta: dataCollection({ dir: config.dir, ...config.meta }),
    doc: docsMdxCollection({ dir: config.dir, ...config.docs }),
  };
}
```
Sources: [packages/content/src/index.ts:35-53](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts#L35-L53)

The underlying `DocsCollection` class extends the base `Collection` implementation from `fuma-content/collections`. During its emit lifecycle (`onEmit.pipe`), it dynamically generates code files for code generation workflows, writing module imports and export definitions for runtime stores.

```typescript
export class DocsCollection extends Collection {
  constructor() {
    super();
    this.onEmit.pipe(async (data, ctx) => {
      const file = await ctx.createCodeGenerator(`${this.name}.ts`, ({ codegen }) => {
        const docName = `${this.name}$doc`;
        const metaName = `${this.name}$meta`;
        codegen.addNamedImport([docName], `./${docName}`);
        codegen.addNamedImport([metaName], `./${metaName}`);
        codegen.addNamedImport(['docsStore'], '@fumadocs/content/runtime');
        codegen.push(`export const ${this.name} = docsStore(${docName}, ${metaName})`);
      });
      data.push(file);
      return data;
    });
  }
```
Sources: [packages/content/src/index.ts:55-70](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts#L55-L70)

> [!NOTE]
> The `DocsCollection` generator creates code files named `<name>.ts` that import the respective document and metadata files, import `docsStore` from `@fumadocs/content/runtime`, and export a combined store constant.
> Sources: [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts#L59-L65)

### Defining Content Schemas and Runtime Stores

Content schemas validate frontmatter attributes against standard schema specifications such as `@Standard-schema/spec`. By default, `docsMdxCollection` and `docsCollection` fall back to `pageSchema` and `metaSchema` imported from `fumadocs-core/source/schema`.

```typescript
export function docsMdxCollection<
  FrontmatterSchema extends StandardSchemaV1 | typeof pageSchema = typeof pageSchema,
>(config: DocsMDXCollectionConfig<FrontmatterSchema>) {
  const { options = {}, frontmatter = pageSchema, ...rest } = config;

  return mdxCollection<FrontmatterSchema>({
    ...rest,
    frontmatter: frontmatter as never,
    async options() {
      const mdxOptions = typeof options === 'object' ? options : await options();
      const { remarkInclude } = await import('fuma-content/plugins/remark/include');
      return mdxPreset({
        ...mdxOptions,
        _include: remarkInclude,
      });
    },
  });
}
```
Sources: [packages/content/src/index.ts:16-33](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts#L16-L33)

The runtime integration layer converts raw MDX and metadata file stores into static source representations consumed by Fumadocs routing and layout utilities. The `toFumadocsSource` function iterates over the list of pages and metadata files, normalizing paths and combining compiled frontmatter properties.

```typescript
export function toFumadocsSource<
  Mdx extends MDXStoreData<PageData> | MDXStoreLazyData<PageData, unknown> = MDXStoreData<
    PageData,
    unknown
  >,
  Meta extends { data: MetaData } = { data: MetaData },
>(mdxStore?: FileCollectionStore<Mdx>, metaStore?: FileCollectionStore<Meta>) {
  const out: StaticSource<{
    pageData: ToPageData<Mdx>;
    metaData: Meta['data'];
  }> = { files: [] };
  for (const page of mdxStore?.list() ?? []) {
    out.files.push({
      type: 'page',
      data: ('compiled' in page
        ? { ...page, ...page.compiled.frontmatter }
        : { ...page, ...page.frontmatter }) as unknown as ToPageData<Mdx>,
      path: page.path,
      absolutePath: page.fullPath,
    });
  }
  for (const file of metaStore?.list() ?? []) {
    out.files.push({
      type: 'meta',
      data: file.data,
      path: file.path,
      absolutePath: file.fullPath,
    });
  }
  return out;
}
```
Sources: [packages/content/src/runtime.ts:12-42](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/runtime.ts#L12-L42)

| Function / Class | Parameters | Return Type | Description |
| :--- | :--- | :--- | :--- |
| `docsMdxCollection` | `config: DocsMDXCollectionConfig<FrontmatterSchema>` | MDX collection instance | Configures MDX collections with preset options and remark include plugins |
| `docsCollection` | `config: DocsCollectionConfig<FrontmatterSchema, MetaSchema>` | `{ index, meta, doc }` | Combines metadata and MDX collections into a unified documentation workspace |
| `DocsCollection` | none | `DocsCollection` class instance | Extends base collections to emit code generator output files during build pipelines |
| `toFumadocsSource` | `mdxStore`, `metaStore` | `StaticSource` | Transforms file collection stores into standard Fumadocs static source structures |
| `docsStore` | `mdxStore`, `metaStore` | `{ toFumadocsSource() }` | Wraps file stores with a method to export static source data |

Sources: [packages/content/src/index.ts:16-53](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts#L16-L53), [packages/content/src/index.ts:55-70](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts#L55-L70), [packages/content/src/runtime.ts:12-56](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/runtime.ts#L12-L56)

> [!WARNING]
> When defining custom options for `docsMdxCollection`, the options parameter can be passed directly as an object or as an asynchronous factory function returning `MDXBundlerPresetOptions`. The runtime automatically injects the `remarkInclude` plugin into the bundler preset.
> Sources: [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts#L11-L31)

## Mounting documentation pages and layouts

### Layout and Preset Resolution

Documentation routes are handled by dynamic page components receiving URL slugs. The [preview server](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/preview-server) loads the runtime configuration and resolves the metadata source before determining whether to use the default `docs` layout preset or the alternative `flux` preset.

```tsx
export default async function DocPage({ slugs }: PageProps<'/docs/[...slugs]'>) {
  const config = await getConfigRuntime();
  const source = await getSource(config);
  const page = source.getPage(slugs);
  const mdPreset = config.layout.presets?.md ?? 'docs';
  let mdPresetComponents: MdPresetComponents;

  if (mdPreset === 'docs') {
    mdPresetComponents = {
      layout: await import('fumadocs-ui/layouts/docs'),
      page: await import('fumadocs-ui/layouts/docs/page'),
    };
  } else {
    mdPresetComponents = {
      layout: await import('fumadocs-ui/layouts/flux'),
      page: await import('fumadocs-ui/layouts/flux/page'),
    };
  }

  return <MdContent slugs={slugs} page={page} components={mdPresetComponents} />;
}
```
Sources: [packages/preview/src/pages/...slugs.tsx:76-96](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/%5B...slugs%5D.tsx#L76-L96)

| Layout Preset | Layout Component Import | Page Component Import |
| :--- | :--- | :--- |
| `docs` | `fumadocs-ui/layouts/docs` | `fumadocs-ui/layouts/docs/page` |
| `flux` | `fumadocs-ui/layouts/flux` | `fumadocs-ui/layouts/flux/page` |

Sources: [packages/preview/src/pages/...slugs.tsx:83-93](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/%5B...slugs%5D.tsx#L83-L93)

> [!NOTE]
> The layout preset is read directly from `config.layout.presets?.md`. If it is not explicitly configured, it defaults to the standard `docs` layout.
> Sources: [packages/preview/src/pages/[...slugs].tsx:80-88](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/[...slugs].tsx#L80-L88)

### Markdown Compilation and Container Rendering

The `MdContent` component builds the surrounding container structure, integrating AI search capabilities if supported by the environment. It checks if the requested page exists in the source tree; if missing, it either presents a home overview cards listing or a standard 404 Not Found view.

```tsx
async function MdContent({
  slugs,
  page,
  components,
}: {
  slugs: string[];
  page?: SourcePage;
  components: MdPresetComponents;
}) {
  const config = await getConfigRuntime();
  const source = await getSource(config);
  const layout = layoutConfig(config);
  const { DocsLayout } = components.layout;
  const { DocsBody, DocsTitle, DocsPage, DocsDescription } = components.page;

  async function renderContainer(children: ReactNode) {
    const hasAI = await isAISupported();
    return (
      <DocsLayout {...await layout.docs()}>
        {hasAI && (
          <AISearch>
            <AISearchPanel />
            <AISearchTrigger
              position="float"
              className={cn(
                buttonVariants({
                  variant: 'secondary',
                  className: 'text-fd-muted-foreground rounded-2xl',
                }),
              )}
            >
              <MessageCircleIcon className="size-4.5" />
              Ask AI
            </AISearchTrigger>
          </AISearch>
        )}

        {children}
      </DocsLayout>
    );
  }
```
Sources: [packages/preview/src/pages/...slugs.tsx:98-138](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/%5B...slugs%5D.tsx#L98-L138)

Markdown strings are processed through a configured compiler instance equipped with remark and rehype plugins for GitHub-Flavored Markdown, math equations, diagrams, heading anchors, package managers, code tabs, and [syntax highlighting](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/core-architecture/syntax-highlighting).

```tsx
const compiler = createMarkdownCompiler({
  remarkPlugins: [
    remarkGfm,
    remarkMath,
    remarkMdxMermaid,
    plugin(remarkHeading, { generateToc: false }),
    plugin(remarkNpm, { persist: { id: 'package-manager' } }),
    remarkCodeTab,
  ],
  remarkRehypeOptions: {
    passThrough: ['mdxJsxFlowElement', 'mdxJsxTextElement'],
  },
  rehypePlugins: [
    rehypeKatex,
    plugin(rehypeCode, { lazy: true, fallbackLanguage: 'text' }),
    plugin(rehypeToc, { exportToc: { as: 'data' } }),
  ],
});
```
Sources: [packages/preview/src/pages/...slugs.tsx:29-46](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/%5B...slugs%5D.tsx#L29-L46)

> [!CAUTION]
> If compilation fails, the catch block intercepts the error and renders a dedicated error view displaying the error message and its stack trace inside a code block.
> Sources: [packages/preview/src/pages/[...slugs].tsx:170-194](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/[...slugs].tsx#L170-L194)

## Running the live preview server

### Standalone CLI Execution and Port Allocation

The preview runtime is executed via the `fumadocs-preview` command-line interface, defined using the `commander` package. The CLI provides two primary commands: `init`, which scaffolds a default configuration file named `fumadocs.config.ts`, and `start`, which serves as the default action for starting the live preview server.

```typescript
program
  .command('init')
  .description('create configuration file')
  .action(async () => {
    if ((await findConfigPath()) === null) {
      await fs.writeFile(
        'fumadocs.config.ts',
        `import { defineConfig } from "fumadocs-preview/config";\n\nexport default defineConfig();`,
      );

      console.log('note: make sure to install `fumadocs-preview` as a dev dependency too.');
    }
  });

program
  .command('start', { isDefault: true })
  .description('start Fumapress')
  .argument('[dirs...]', 'a list of content directories to view')
  .option('-p, --port <PORT>')
  .option('-h, --host <HOST>')
  .action(async (dirs: string[] | undefined, options: { port?: string; host?: string }) => {
    await runStart({ ...options, dirs });
  });
```
Sources: [packages/preview/src/cli/index.ts:8-30](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/index.ts#L8-L30)

| Command | Description | Arguments / Options |
| :--- | :--- | :--- |
| `init` | Create configuration file | None |
| `start` | Start Fumapress | `[dirs...]`, `-p, --port <PORT>`, `-h, --host <HOST>` |

Sources: [packages/preview/src/cli/index.ts:8-30](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/index.ts#L8-L30)

When the `start` command executes, `runStart` loads environment variables via `loadEnv()` and overrides `NODE_ENV` to `production`. It invokes `getFreePort` starting from a default port of `8080` (or a custom port specified via options) to find an available TCP port by probing network sockets with `net.createServer()`. If the port is occupied with an `EADDRINUSE` error, it automatically increments the port number until an open socket is secured.

```typescript
async function getFreePort(startPort: number): Promise<number> {
  for (let port = startPort; ; port++) {
    try {
      await new Promise<void>((resolve, reject) => {
        const srv = net
          .createServer()
          .once('error', reject)
          .once('listening', () => srv.close(() => resolve()))
          .listen(port);
      });
      return port;
    } catch (err) {
      if ((err as NodeJS.ErrnoException).code !== 'EADDRINUSE') {
        throw err;
      }
    }
  }
}
```
Sources: [packages/preview/src/cli/commands.ts:36-53](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/commands.ts#L36-L53)

> [!WARNING]
> If `getFreePort` encounters any socket error other than `EADDRINUSE`, it does not retry and immediately rejects with that error.
> Sources: [packages/preview/src/cli/commands.ts:47-51](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/commands.ts#L47-L51)

### Environment Setup and Server Bootstrapping

Prior to launching the server bundle, `runStart` configures several required process environment variables to pass parameters down to the Waku server runtime.

```typescript
  if (host) {
    process.env.HOST = host;
  }
  process.env.PORT = String(port);
  process.env.ROOT_DIR = process.cwd();
  if (!dirs || dirs.length === 0) {
    dirs = [''];
  }
  process.env.DEFAULT_PROJECT_DIR = JSON.stringify(dirs.map((v) => path.resolve(v)));
  process.env.HOT_RELOAD = '1';
  process.chdir(baseDir);
  await import(serveFileUrl);

  console.log(`ready: Listening on http://${host || 'localhost'}:${port}/`);
}
```
Sources: [packages/preview/src/cli/commands.ts:20-34](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/commands.ts#L20-L34)

| Environment Variable | Assigned Value / Logic | Purpose |
| :--- | :--- | :--- |
| `HOST` | `host` option parameter (if provided) | Binds the server to a specific network host address |
| `PORT` | `String(port)` | Sets the listening port resolved by `getFreePort` |
| `ROOT_DIR` | `process.cwd()` | Establishes the root directory context for project file resolution |
| `DEFAULT_PROJECT_DIR` | JSON string of `dirs` mapped via `path.resolve()` | Specifies content directories to view; defaults to `['']` if empty |
| `HOT_RELOAD` | `'1'` | Enables dynamic hot-reloading behavior in the preview environment |

Sources: [packages/preview/src/cli/commands.ts:20-29](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/commands.ts#L20-L29)

> [!TIP]
> After configuring environment variables, `runStart` changes the working directory to `baseDir` and dynamically imports the compiled Node server entry point (`dist/waku/serve-node.js`) using `pathToFileURL`.
> Sources: [packages/preview/src/cli/commands.ts:5-31](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/commands.ts#L5-L31)

## Enabling dynamic local markdown updates

### Local Markdown Source Configuration and Compilation

The `@fumadocs/local-md` package provides the `localMd` factory function to manage local markdown and metadata files as a content source. It accepts a `LocalMarkdownConfig` object defining the root content directory (`dir`), glob patterns (`include`), frontmatter and metadata schemas, and optional compiler settings.

```typescript
export interface LocalMarkdownConfig<
  FrontmatterSchema extends StandardSchemaV1,
  MetaSchema extends StandardSchemaV1,
> extends MarkdownCompilerOptions {
  /**
   * root directory for content files.
   */
  dir: string;
  /**
   * a list of glob patterns, customize the content files to be scanned.
   */
  include?: string[];
  rendererOptions?: Pick<MarkdownRendererASTOptions, 'executor'>;

  frontmatterSchema?: FrontmatterSchema;
  metaSchema?: MetaSchema;
}
```
Sources: [packages/local-md/src/index.ts:24-40](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts#L24-L40)

| Configuration Property | Type | Default / Purpose |
| :--- | :--- | :--- |
| `dir` | `string` | Root directory for content files |
| `include` | `string[]` | Glob patterns to scan content files |
| `frontmatterSchema` | `StandardSchemaV1` | Validation schema for page frontmatter |
| `metaSchema` | `StandardSchemaV1` | Validation schema for metadata files |

Sources: [packages/local-md/src/index.ts:24-40](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts#L24-L40)

When `staticSource` or `dynamicSource` is invoked, `createFiles` reads pages and metadata via internal storage and builds virtual files. Each page includes a `load` function that checks a weak map cache (`compilerCache`) or compiles the markdown content using `createMarkdownCompiler`. Depending on the compiler result type, it delegates to `fromAst` or `fromJS` to generate the final renderer.

```typescript
async load() {
  let promise = compilerCache.get(page);
  if (!promise) {
    promise = compiler.compile({
      path: page.absolutePath,
      value: page.content,
      data: { frontmatter: page.frontmatter },
    });
  }

  const res = await promise;
  return res.type === 'ast'
    ? fromAst({
        tree: res.tree,
        filePath: res.file.path,
        rehypeToc: res.file.data.rehypeToc,
        structuredData: res.file.data.structuredData,
        ...config.rendererOptions,
      })
    : fromJS({
        code: res.code,
        filePath: res.file.path,
        baseUrl: pathToFileURL(res.file.path).href,
        structuredData: res.file.data.structuredData,
      });
},
```
Sources: [packages/local-md/src/index.ts:125-150](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts#L125-L150)

> [!NOTE]
> The `compilerCache` uses a `WeakMap<RawPage<unknown>, Promise<CompileResult>>` to memoize compilation promises per raw page object and prevent redundant parsing during development.
> Sources: [packages/local-md/src/index.ts:90-92](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts#L90-L92)

### Hot-Reloading via Development Server and WebSocket Clients

To support real-time editing, the `localMd` instance exposes a `devServer` method and an `invalidateFile` mechanism. When `devServer` is called, it connects to a WebSocket server, sends a `watch-dir` message with the configured directory and glob patterns, and listens for change events to invalidate modified files.

```typescript
async devServer(url = getDevServerUrlFromEnv()) {
  if (!url) {
    console.warn(
      `[@fumadocs/local-md] dev server URL could not be found, try passing the URL to devServer() explicitly instead`,
    );
    return;
  }

  const { connectDevServer } = await import('@/dev/node-client');
  const conn = connectDevServer(url);
  conn.send({
    type: 'watch-dir',
    dir: path.resolve(config.dir),
    includes: config.include ?? defaultInclude,
  });

  conn.subscribe((event) => {
    if (event.type === 'change') {
      this.invalidateFile(event.absolutePath);
    }
  });
},
```
Sources: [packages/local-md/src/index.ts:174-195](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts#L174-L195)

The React client component `DevClient` establishes a WebSocket connection to the development server using the URL resolved by `getDevServerUrlFromEnv`. Upon receiving an incoming message, it decodes the event using `decodeDevEvent`. If the decoded event type is a `change`, it logs the update and calls `router.refresh()` to trigger an immediate page reload in the framework router.

```typescript
export function DevClient(): ReactNode {
  const router = useRouter();

  const onUpdate = useEffectEvent((event: DevServerEvent) => {
    if (event.type === 'change') {
      console.log(`[@fumadocs/local-md] "${event.absolutePath}" updated`);
      router.refresh();
    }
  });

  useEffect(() => {
    const url = getDevServerUrlFromEnv();
    if (!url) return;

    const ws = new WebSocket(url);
    ws.onopen = () => {
      console.log(`[@fumadocs/local-md] connected to dev server at ${url}`);
    };

    ws.onmessage = (event) => {
      const decoded = decodeDevEvent(String(event.data));
      if (decoded) onUpdate(decoded);
    };

    ws.onclose = () => {
      console.log(`[@fumadocs/local-md] disconnected from dev server at ${url}`);
    };

    return () => {
      ws.close();
    };
  }, []);

```
Sources: [packages/local-md/src/dev/react-client.ts:6-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/react-client.ts#L6-L38)

> [!WARNING]
> If `getDevServerUrlFromEnv` returns no URL during client initialization or inside `devServer()`, the connection attempt is aborted with a warning message or early return.
> Sources: [packages/local-md/src/index.ts:174-180](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts#L174-L180), [packages/local-md/src/dev/react-client.ts:16-18](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/react-client.ts#L16-L18)

## Related

- [Overview](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/getting-started/overview)
- [Project Structure](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/getting-started/project-structure)
- [Create App Generator](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/create-app-generator)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/llms.txt) for all pages in this wiki.
