Installation
Option A: CLI (Recommended)
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:
npx @farming-labs/docs initpnpm dlx @farming-labs/docs inityarn dlx @farming-labs/docs initbunx @farming-labs/docs initThe 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:
- Detect your framework (Next.js, TanStack Start, SvelteKit, Astro, or Nuxt), or ask you to pick one
- Ask you to pick a theme (including Create your own theme, which prompts for a theme name and scaffolds
themes/<name>.tsandthemes/<name>.css) - Ask about path aliases and which global CSS file to use (defaults are offered; press Enter to accept)
- Ask for the docs entry path (default:
docs— press Enter to accept) - 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
- Generate config, layout, CSS, and sample pages
- 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:
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 # Astropnpm 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 # Astroyarn 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 # Astrobunx @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 # AstroReplace 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
npm install @farming-labs/docs @farming-labs/theme @farming-labs/nextpnpm add @farming-labs/docs @farming-labs/theme @farming-labs/nextyarn add @farming-labs/docs @farming-labs/theme @farming-labs/nextbun add @farming-labs/docs @farming-labs/theme @farming-labs/next2. Create 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
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.
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>
);
}
RootProviderautomatically configures the search API endpoint (/api/docs) and handles theme persistence. ThesuppressHydrationWarningon<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).
@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-startedEach page uses frontmatter for metadata:
---
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.tsxandapp/docs/layout.tsx, or check Configuration for all available options.
1. Install packages
npm install @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-startpnpm add @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-startyarn add @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-startbun add @farming-labs/docs @farming-labs/theme @farming-labs/tanstack-start2. Create 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
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
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
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
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
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()} />,
});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
@import "tailwindcss";
@import "@farming-labs/theme/default/css";9. Add the docs API route
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
npm install @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-themepnpm add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-themeyarn add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-themebun add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme2. Create 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
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:
<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:
export { load } from "../../lib/docs.server";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):
export { GET, POST } from "../../../lib/docs.server";Production note: The
docs.server.tsfile usesimport.meta.globto 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).
@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-startedEach page uses frontmatter for metadata:
---
title: "Getting Started"
description: "Your first doc page"
icon: "rocket"
---
# Getting Started
Your content here.1. Install packages
npm install @farming-labs/docs @farming-labs/astro @farming-labs/astro-themepnpm add @farming-labs/docs @farming-labs/astro @farming-labs/astro-themeyarn add @farming-labs/docs @farming-labs/astro @farming-labs/astro-themebun add @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme2. Create 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
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:
---
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:
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:
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-startedEach page uses frontmatter for metadata:
---
title: "Getting Started"
description: "Your first doc page"
icon: "rocket"
---
# Getting Started
Your content here.1. Install packages
npm install @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-themepnpm add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-themeyarn add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-themebun add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme2. Create 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).
export default defineNuxtConfig({
css: ["@farming-labs/nuxt-theme/fumadocs/css"],
nitro: {
serverAssets: [{ baseName: "docs", dir: "../docs" }],
},
});4. Create 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
<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
serverAssetsconfig innuxt.config.tstells 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-startedEach page uses frontmatter for metadata:
---
title: "Getting Started"
description: "Your first doc page"
icon: "rocket"
---
# Getting Started
Your content here.How is this guide?