Home /

docs

Installation

Starting from scratch? Use --template with --name <project> to bootstrap a new docs project (Next.js, TanStack Start, Nuxt, SvelteKit, or Astro). No prompts — the CLI creates the project folder and runs install. See the --template section below.

Adding docs to an existing app? Run the init command inside your Next.js, TanStack Start, SvelteKit, Astro, or Nuxt project:

terminal
npx @farming-labs/docs init
terminal
pnpm dlx @farming-labs/docs init
terminal
yarn dlx @farming-labs/docs init
terminal
bunx @farming-labs/docs init

The CLI will first ask: Are you adding docs to an existing project or starting fresh? Choose Existing project to add docs to the current directory. It will then:

  1. Detect your framework (Next.js, TanStack Start, SvelteKit, Astro, or Nuxt), or ask you to pick one
  2. Ask you to pick a theme (including Create your own theme, which prompts for a theme name and scaffolds themes/<name>.ts and themes/<name>.css)
  3. Ask about path aliases and which global CSS file to use (defaults are offered; press Enter to accept)
  4. Ask for the docs entry path (default: docs — press Enter to accept)
  5. Optionally scaffold i18n by selecting locales, adding custom locale codes, and choosing a default locale TanStack Start currently skips the built-in i18n scaffold so the generated routes stay minimal and working
  6. Generate config, layout, CSS, and sample pages
  7. Install dependencies and optionally start the dev server

Prompts that show a placeholder (e.g. entry path, theme name) use that value as the default — you can press Enter to skip typing. See CLI — Init flow for the full sequence.

If you enable i18n, the CLI creates locale folders like docs/en and docs/fr, writes the i18n block to docs.config, and scaffolds the framework-specific files needed for routes like /docs?lang=en.

To upgrade all docs packages to the latest later, run from your project root: npx @farming-labs/docs upgrade (framework is auto-detected). See CLI — Upgrade for options.

Starting from scratch or want a full example? Use --template with --name <project> to bootstrap a ready-to-run docs app. The CLI creates the project folder, bootstraps it with the chosen framework, and runs install:

terminal
npx @farming-labs/docs init --template next --name my-docs     # Next.js
npx @farming-labs/docs init --template tanstack-start --name my-docs # TanStack Start
npx @farming-labs/docs init --template nuxt --name my-docs     # Nuxt
npx @farming-labs/docs init --template sveltekit --name my-docs # SvelteKit
npx @farming-labs/docs init --template astro --name my-docs    # Astro
terminal
pnpm dlx @farming-labs/docs init --template next --name my-docs     # Next.js
pnpm dlx @farming-labs/docs init --template tanstack-start --name my-docs # TanStack Start
pnpm dlx @farming-labs/docs init --template nuxt --name my-docs     # Nuxt
pnpm dlx @farming-labs/docs init --template sveltekit --name my-docs # SvelteKit
pnpm dlx @farming-labs/docs init --template astro --name my-docs    # Astro
terminal
yarn dlx @farming-labs/docs init --template next --name my-docs     # Next.js
yarn dlx @farming-labs/docs init --template tanstack-start --name my-docs # TanStack Start
yarn dlx @farming-labs/docs init --template nuxt --name my-docs     # Nuxt
yarn dlx @farming-labs/docs init --template sveltekit --name my-docs # SvelteKit
yarn dlx @farming-labs/docs init --template astro --name my-docs    # Astro
terminal
bunx @farming-labs/docs init --template next --name my-docs     # Next.js
bunx @farming-labs/docs init --template tanstack-start --name my-docs # TanStack Start
bunx @farming-labs/docs init --template nuxt --name my-docs     # Nuxt
bunx @farming-labs/docs init --template sveltekit --name my-docs # SvelteKit
bunx @farming-labs/docs init --template astro --name my-docs    # Astro

Replace my-docs with your project name. Then run cd my-docs and start the dev server (npm run dev, pnpm run dev, yarn dev, or bun run dev).

Option B: Manual Setup

Theme CSS in global styles

For each framework below, add your theme's CSS to your global stylesheet (e.g. app/global.css, src/app.css, or nuxt.config). The import path must match the theme you use in docs.config — e.g. default, greentree, darksharp. Without it, docs pages will lack the correct styling.

1. Install packages

terminal
npm install @farming-labs/docs @farming-labs/theme @farming-labs/next
terminal
pnpm add @farming-labs/docs @farming-labs/theme @farming-labs/next
terminal
yarn add @farming-labs/docs @farming-labs/theme @farming-labs/next
terminal
bun add @farming-labs/docs @farming-labs/theme @farming-labs/next

2. Create docs.config.tsx

docs.config.tsx
import { defineDocs } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/theme";

export default defineDocs({
  entry: "docs",
  theme: fumadocs(),
  metadata: {
    titleTemplate: "%s – Docs",
    description: "My documentation site",
  },
});

3. Create next.config.ts

next.config.ts
import { withDocs } from "@farming-labs/next/config";
export default withDocs({});

4. Set up RootProvider in app/layout.tsx

Wrap your app with RootProvider from @farming-labs/theme. This enables search, theme switching, and AI chat across all pages.

app/layout.tsx
import { RootProvider } from "@farming-labs/theme";
import "./global.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <RootProvider>{children}</RootProvider>
      </body>
    </html>
  );
}

RootProvider automatically configures the search API endpoint (/api/docs) and handles theme persistence. The suppressHydrationWarning on <html> prevents React warnings from the theme class injection.

5. Import theme CSS in app/global.css

Add your theme's CSS to your global stylesheet so docs styling applies. Use the import that matches the theme in your config (e.g. default below; for greentree use @farming-labs/theme/greentree/css).

app/global.css
@import "tailwindcss";
@import "@farming-labs/theme/default/css";

6. Write your first doc

Create an MDX file under app/docs/:

app/docs/
  page.mdx              # /docs
  getting-started/
    page.mdx            # /docs/getting-started

Each page uses frontmatter for metadata:

app/docs/getting-started/page.mdx
---
title: "Getting Started"
description: "Your first doc page"
icon: "rocket"
---

# Getting Started

Your content here.

That's it — the docs layout (app/docs/layout.tsx) is auto-generated by withDocs(). The framework handles routing, MDX compilation, and metadata from your config.

See CLI for all generated files including the full app/layout.tsx and app/docs/layout.tsx, or check Configuration for all available options.

1. Install packages

terminal
npm install @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-start
terminal
pnpm add @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-start
terminal
yarn add @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-start
terminal
bun add @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-start

2. Create docs.config.tsx

docs.config.tsx
import { defineDocs } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/theme";

export default defineDocs({
  entry: "docs",
  contentDir: "docs",
  theme: fumadocs(),
  nav: {
    title: "My Docs",
    url: "/docs",
  },
  metadata: {
    titleTemplate: "%s – Docs",
    description: "My documentation site",
  },
});

3. Update vite.config.ts

vite.config.ts
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import { docsMdx } from "@farming-labs/tanstack-start/vite";

export default defineConfig({
  plugins: [tailwindcss(), docsMdx(), tsconfigPaths({ ignoreConfigErrors: true }), tanstackStart()],
});

4. Create src/lib/docs.server.ts

src/lib/docs.server.ts
import { createDocsServer } from "@farming-labs/tanstack-start/server";
import docsConfig from "../../docs.config";

export const docsServer = createDocsServer({
  ...docsConfig,
  rootDir: process.cwd(),
});

5. Create src/lib/docs.functions.ts

src/lib/docs.functions.ts
import { createServerFn } from "@tanstack/react-start";
import { docsServer } from "./docs.server";

export const loadDocPage = createServerFn({ method: "GET" })
  .inputValidator((data: { pathname: string; locale?: string }) => data)
  .handler(async ({ data }) => docsServer.load(data));

6. Wrap the app with RootProvider

src/routes/__root.tsx
import appCss from "@/styles/app.css?url";
import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router";
import { RootProvider } from "@farming-labs/theme/tanstack";

export const Route = createRootRoute({
  head: () => ({
    links: [{ rel: "stylesheet", href: appCss }],
    meta: [
      { charSet: "utf-8" },
      { name: "viewport", content: "width=device-width, initial-scale=1" },
      { title: "TanStack Start Docs" },
    ],
  }),
  component: RootComponent,
});

function RootComponent() {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <HeadContent />
      </head>
      <body>
        <RootProvider>
          <Outlet />
        </RootProvider>
        <Scripts />
      </body>
    </html>
  );
}

7. Render docs routes

src/routes/docs.index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { TanstackDocsPage } from "@farming-labs/tanstack-start/react";
import { loadDocPage } from "@/lib/docs.functions";
import docsConfig from "../../docs.config";

export const Route = createFileRoute("/docs/")({
  loader: () => loadDocPage({ data: { pathname: "/docs" } }),
  component: () => <TanstackDocsPage config={docsConfig} data={Route.useLoaderData()} />,
});
src/routes/docs.$.tsx
import { createFileRoute, notFound } from "@tanstack/react-router";
import { TanstackDocsPage } from "@farming-labs/tanstack-start/react";
import { loadDocPage } from "@/lib/docs.functions";
import docsConfig from "../../docs.config";

export const Route = createFileRoute("/docs/$")({
  loader: async ({ location }) => {
    try {
      return await loadDocPage({ data: { pathname: location.pathname } });
    } catch (error) {
      if (
        error &&
        typeof error === "object" &&
        "status" in error &&
        (error as { status?: unknown }).status === 404
      ) {
        throw notFound();
      }
      throw error;
    }
  },
  component: () => <TanstackDocsPage config={docsConfig} data={Route.useLoaderData()} />,
});

8. Import the theme CSS in src/styles/app.css

src/styles/app.css
@import "tailwindcss";
@import "@farming-labs/theme/default/css";

9. Add the docs API route

src/routes/api.docs.ts
import { createFileRoute } from "@tanstack/react-router";
import { docsServer } from "@/lib/docs.server";

export const Route = createFileRoute("/api/docs")({
  server: {
    handlers: {
      GET: async ({ request }) => docsServer.GET({ request }),
      POST: async ({ request }) => docsServer.POST({ request }),
    },
  },
});

That is the full manual setup. The built-in TanstackDocsPage renderer handles MDX page modules for you, so there is no extra docs-page.tsx or doc-modules.ts file to maintain.

1. Install packages

terminal
npm install @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme
terminal
pnpm add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme
terminal
yarn add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme
terminal
bun add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme

2. Create src/lib/docs.config.ts

src/lib/docs.config.ts
import { defineDocs } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/svelte-theme";

export default defineDocs({
  entry: "docs",
  contentDir: "docs",
  theme: fumadocs(),
  nav: {
    title: "My Docs",
    url: "/docs",
  },
  metadata: {
    titleTemplate: "%s – Docs",
    description: "My documentation site",
  },
});

3. Create src/lib/docs.server.ts

src/lib/docs.server.ts
import { createDocsServer } from "@farming-labs/svelte/server";
import config from "./docs.config";

const contentFiles = import.meta.glob("/docs/**/*.{md,mdx,svx}", {
  query: "?raw",
  import: "default",
  eager: true,
}) as Record<string, string>;

export const { load, GET, POST } = createDocsServer({
  ...config,
  _preloadedContent: contentFiles,
});

4. Create route files

src/routes/docs/+layout.svelte:

src/routes/docs/+layout.svelte
<script>
  import { DocsLayout } from "@farming-labs/svelte-theme";
  import config from "../../lib/docs.config";

  let { data, children } = $props();
</script>

<DocsLayout tree={data.tree} {config}>
  {@render children()}
</DocsLayout>

src/routes/docs/+layout.server.js:

src/routes/docs/+layout.server.js
export { load } from "../../lib/docs.server";

src/routes/docs/[...slug]/+page.svelte:

src/routes/docs/[...slug]/+page.svelte
<script>
  import { DocsContent } from "@farming-labs/svelte-theme";
  import config from "../../../lib/docs.config";

  let { data } = $props();
</script>

<DocsContent {data} {config} />

src/routes/api/docs/+server.js (for search and AI):

src/routes/api/docs/+server.js
export { GET, POST } from "../../../lib/docs.server";

Production note: The docs.server.ts file uses import.meta.glob to bundle your markdown files at build time. This is required for serverless deployments (Vercel, Netlify, etc.) where the filesystem is not available at runtime.

5. Import theme CSS in src/app.css

Add your theme's CSS to your global stylesheet so docs styling applies. Use the import that matches the theme in your config (e.g. fumadocs below; for greentree use @farming-labs/svelte-theme/greentree/css).

src/app.css
@import "@farming-labs/svelte-theme/fumadocs/css";

6. Write your first doc

Create Markdown files under docs/:

docs/
  page.md               # /docs
  getting-started/
    page.md             # /docs/getting-started

Each page uses frontmatter for metadata:

docs/getting-started/page.md
---
title: "Getting Started"
description: "Your first doc page"
icon: "rocket"
---

# Getting Started

Your content here.

1. Install packages

terminal
npm install @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme
terminal
pnpm add @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme
terminal
yarn add @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme
terminal
bun add @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme

2. Create src/lib/docs.config.ts

src/lib/docs.config.ts
import { defineDocs } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/astro-theme";

export default defineDocs({
  entry: "docs",
  contentDir: "docs",
  theme: fumadocs(),
  nav: { title: "My Docs", url: "/docs" },
  metadata: { titleTemplate: "%s – My Docs" },
});

3. Create src/lib/docs.server.ts

src/lib/docs.server.ts
import { createDocsServer } from "@farming-labs/astro/server";
import config from "./docs.config";

const contentFiles = import.meta.glob("/docs/**/*.{md,mdx}", {
  query: "?raw",
  import: "default",
  eager: true,
}) as Record<string, string>;

export const { load, GET, POST } = createDocsServer({
  ...config,
  _preloadedContent: contentFiles,
});

4. Create page routes

Import your theme's CSS in each docs page (or in a global CSS file and import that). The example below uses @farming-labs/astro-theme/css (default); for other themes use e.g. @farming-labs/astro-theme/greentree/css.

src/pages/docs/index.astro and src/pages/docs/[...slug].astro:

src/pages/docs/[...slug].astro
---
import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
import config from "../../lib/docs.config";
import { load } from "../../lib/docs.server";
import "@farming-labs/astro-theme/css";

const data = await load(Astro.url.pathname);
---

<html lang="en">
  <head><title>{data.title} – Docs</title></head>
  <body>
    <DocsLayout tree={data.tree} config={config}>
      <DocsContent data={data} config={config} />
    </DocsLayout>
  </body>
</html>

5. Create API route

src/pages/api/docs.ts:

src/pages/api/docs.ts
import type { APIRoute } from "astro";
import { GET as docsGET, POST as docsPOST } from "../../lib/docs.server";

export const GET: APIRoute = async ({ request }) => docsGET({ request });
export const POST: APIRoute = async ({ request }) => docsPOST({ request });

6. Astro config

Enable SSR in astro.config.mjs:

astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({ output: "server" });

7. Write your first doc

Create Markdown files under docs/:

docs/
  page.md               # /docs
  getting-started/
    page.md             # /docs/getting-started

Each page uses frontmatter for metadata:

docs/getting-started/page.md
---
title: "Getting Started"
description: "Your first doc page"
icon: "rocket"
---

# Getting Started

Your content here.

1. Install packages

terminal
npm install @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme
terminal
pnpm add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme
terminal
yarn add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme
terminal
bun add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme

2. Create docs.config.ts

docs.config.ts
import { defineDocs } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/nuxt-theme";

export default defineDocs({
  entry: "docs",
  contentDir: "docs",
  theme: fumadocs(),
  nav: {
    title: "My Docs",
    url: "/docs",
  },
  metadata: {
    titleTemplate: "%s – Docs",
    description: "My documentation site",
  },
});

3. Configure nuxt.config.ts

Add your theme's CSS via the css array so docs styling applies. Use the path that matches the theme in your config (e.g. fumadocs below; for greentree use @farming-labs/nuxt-theme/greentree/css).

nuxt.config.ts
export default defineNuxtConfig({
  css: ["@farming-labs/nuxt-theme/fumadocs/css"],
  nitro: {
    serverAssets: [{ baseName: "docs", dir: "../docs" }],
  },
});

4. Create server/api/docs.ts

server/api/docs.ts
import { defineDocsHandler } from "@farming-labs/nuxt/server";
import config from "../../docs.config";

export default defineDocsHandler(config, useStorage);

5. Create pages/docs/[...slug].vue

pages/docs/[...slug].vue
<script setup lang="ts">
import { DocsLayout, DocsContent } from "@farming-labs/nuxt-theme";
import config from "~/docs.config";

const route = useRoute();
const pathname = computed(() => route.path);

const { data, error } = await useFetch("/api/docs", {
  query: { pathname },
  watch: [pathname],
});

if (error.value) {
  throw createError({ statusCode: 404, statusMessage: "Page not found" });
}
</script>

<template>
  <div v-if="data" class="fd-docs-wrapper">
    <DocsLayout :tree="data.tree" :config="config">
      <DocsContent :data="data" :config="config" />
    </DocsLayout>
  </div>
</template>

Nuxt uses Nitro's server assets to load markdown files at runtime. The serverAssets config in nuxt.config.ts tells Nitro where your docs directory is.

6. Write your first doc

Create Markdown files under docs/:

docs/
  page.md               # /docs
  getting-started/
    page.md             # /docs/getting-started

Each page uses frontmatter for metadata:

docs/getting-started/page.md
---
title: "Getting Started"
description: "Your first doc page"
icon: "rocket"
---

# Getting Started

Your content here.