Documentation System
Understanding and extending the Fumadocs-based documentation system in ProductReady
Documentation System
ProductReady uses Fumadocs as its documentation framework, providing a modern, fast, and feature-rich documentation experience. This guide explains the documentation architecture and how to extend it.
Content Collections Overview
ProductReady has four content collections, each serving a different purpose:
| Collection | Directory | Route | Purpose |
|---|---|---|---|
docs | content/docs/ | /docs/* | Technical documentation, guides, API reference |
blog | content/blog/ | /blog/* | Company blog, announcements, tutorials |
pages | content/pages/ | /pages/* | Static pages (about, terms, privacy) |
releaseNotes | content/release-notes/ | /release-notes/* | Version changelogs and release notes |
Directory Structure
content/
├── docs/
│ ├── en/ # English documentation
│ │ ├── meta.json # Sidebar navigation
│ │ ├── index.mdx # Docs landing page
│ │ ├── quick-start.mdx
│ │ ├── (api)/ # Route group for API docs
│ │ │ └── ...
│ │ └── ...
│ └── zh-CN/ # Chinese documentation (optional)
│ └── ...
├── blog/
│ ├── en/
│ │ ├── meta.json
│ │ └── welcome.mdx
│ └── ...
├── pages/
│ ├── en/
│ │ ├── about.mdx
│ │ ├── terms-of-service.mdx
│ │ └── privacy-policy.mdx
│ └── ...
└── release-notes/
├── en/
│ ├── meta.json
│ ├── index.mdx
│ └── v1.0.0.mdx
└── ...Collection Configuration
Collections are defined in source.config.ts:
import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from "fumadocs-mdx/config";
import { z } from "zod";
// Main documentation
export const docs = defineDocs({
dir: "content/docs",
docs: {
schema: frontmatterSchema.extend({
full: z.boolean().optional(), // Full-width layout
_openapi: z.object({...}).optional(), // OpenAPI metadata
}),
postprocess: { includeProcessedMarkdown: true },
},
meta: { schema: metaSchema },
});
// Blog posts
export const blog = defineDocs({
dir: "content/blog",
docs: {
schema: frontmatterSchema.extend({
date: z.string().or(z.date()).optional(),
author: z.string().optional(),
}),
postprocess: { includeProcessedMarkdown: true },
},
});
// Static pages
export const pages = defineDocs({
dir: "content/pages",
docs: {
schema: frontmatterSchema,
postprocess: { includeProcessedMarkdown: true },
},
});
// Release notes
export const releaseNotes = defineDocs({
dir: "content/release-notes",
docs: {
schema: frontmatterSchema.extend({
date: z.string().or(z.date()).optional(),
version: z.string().optional(),
}),
postprocess: { includeProcessedMarkdown: true },
},
});Source Loaders
Each collection needs a loader in src/lib/source.ts:
import { blog, docs, pages, releaseNotes } from "fumadocs-mdx:collections/server";
import { loader } from "fumadocs-core/source";
import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons";
import { i18n } from "@/lib/i18n/config";
export const source = loader({
baseUrl: "/docs",
source: docs.toFumadocsSource(),
plugins: [lucideIconsPlugin()],
i18n,
});
export const blogSource = loader({
baseUrl: "/blog",
source: blog.toFumadocsSource(),
plugins: [lucideIconsPlugin()],
i18n,
});
export const pagesSource = loader({
baseUrl: "/pages",
source: pages.toFumadocsSource(),
plugins: [lucideIconsPlugin()],
i18n,
});
export const releaseNotesSource = loader({
baseUrl: "/release-notes",
source: releaseNotes.toFumadocsSource(),
plugins: [lucideIconsPlugin()],
i18n,
});MDX Frontmatter
Every MDX file requires frontmatter:
---
title: Page Title
description: A brief description for SEO and previews
---
# Content starts hereExtended Frontmatter by Collection
Docs:
---
title: API Reference
description: Complete API documentation
full: true # Optional: full-width layout
---Blog:
---
title: Announcing v2.0
description: Major release with new features
date: 2025-01-15
author: John Doe
---Release Notes:
---
title: v1.0.0 Release Notes
description: Initial public release
date: 2025-01-01
version: 1.0.0
---Sidebar Navigation (meta.json)
The meta.json file controls sidebar navigation:
{
"title": "Documentation",
"pages": [
"index",
"---[Rocket]Getting Started---",
"quick-start",
"project-structure",
"---[Shield]Core Features---",
"authentication",
"database",
"---[Link]Links---",
"[History][Release Notes](/release-notes)",
"[House][Home](/)",
"external:[Github](https://github.com/example/repo)"
]
}Syntax Reference
| Pattern | Description | Example |
|---|---|---|
"page-name" | Link to MDX file | "quick-start" → quick-start.mdx |
"---Title---" | Separator with label | "---Getting Started---" |
"---[Icon]Title---" | Separator with Lucide icon | "---[Rocket]Getting Started---" |
"[Icon][Label](url)" | Internal link with icon | "[House][Home](/)" |
"external:[Label](url)" | External link | "external:[Github](https://github.com)" |
"(folder)" | Route group (no URL segment) | "(api)" |
Adding a New Documentation Page
Step 1: Create the MDX file
# Create new doc page
touch content/docs/en/my-new-feature.mdx---
title: My New Feature
description: Learn how to use the new feature
---
# My New Feature
Content goes here...Step 2: Add to sidebar
Edit content/docs/en/meta.json:
{
"pages": [
"index",
"---[Rocket]Getting Started---",
"quick-start",
"my-new-feature", // Add here
...
]
}Step 3: Regenerate types
pnpm fumadocs-mdxAdding a New Content Collection
To add a completely new collection (e.g., tutorials):
Step 1: Define collection in source.config.ts
export const tutorials = defineDocs({
dir: "content/tutorials",
docs: {
schema: frontmatterSchema.extend({
difficulty: z.enum(["beginner", "intermediate", "advanced"]).optional(),
duration: z.string().optional(),
}),
postprocess: { includeProcessedMarkdown: true },
},
});Step 2: Add loader in src/lib/source.ts
import { tutorials } from "fumadocs-mdx:collections/server";
export const tutorialsSource = loader({
baseUrl: "/tutorials",
source: tutorials.toFumadocsSource(),
plugins: [lucideIconsPlugin()],
i18n,
});Step 3: Create content directory
mkdir -p content/tutorials/enCreate content/tutorials/en/meta.json:
{
"title": "Tutorials",
"root": true,
"pages": ["index"]
}Create content/tutorials/en/index.mdx:
---
title: Tutorials
description: Step-by-step tutorials to master ProductReady
---
# Tutorials
Welcome to our tutorials section!Step 4: Create route pages
Create src/app/[lang]/tutorials/layout.tsx and src/app/[lang]/tutorials/[[...slug]]/page.tsx following the pattern of existing collections (e.g., /release-notes).
Step 5: Regenerate and verify
pnpm fumadocs-mdx
pnpm typecheckInternationalization (i18n)
Each collection supports multiple languages:
content/docs/
├── en/ # Default/fallback
├── zh-CN/ # Simplified Chinese
├── zh-TW/ # Traditional Chinese
├── ja/ # Japanese
├── ko/ # Korean
└── ...Add a new language:
- Create the directory:
content/docs/fr/ - Copy
meta.jsonand translate - Create translated MDX files
- Update
src/lib/i18n/config.tsif needed
Best Practices
Content Organization
- One topic per page: Keep pages focused on a single concept
- Use descriptive filenames:
authentication.mdxnotauth.mdx - Group related content: Use folders for sections with 3+ pages
- Use route groups:
(api)/for pages that shouldn't add URL segments
Writing Style
- Start with a summary: First paragraph should explain what the page covers
- Use headings: Structure content with H2, H3 for navigation
- Include examples: Code blocks with real, working examples
- Add cross-references: Link to related documentation
SEO & Discoverability
- Descriptive titles: Clear, searchable page titles
- Meta descriptions: 150-160 character descriptions
- Consistent naming: Use the same terminology across docs
Useful Commands
# Regenerate MDX types after adding/modifying collections
pnpm fumadocs-mdx
# Type check to verify no errors
pnpm typecheck
# Start dev server to preview
pnpm dev
# Generate OpenAPI docs from running server
pnpm openapi:generateRelated Resources
- Fumadocs Documentation
- MDX Documentation
- Lucide Icons (for sidebar icons)