GameCraftGameCraft

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:

CollectionDirectoryRoutePurpose
docscontent/docs//docs/*Technical documentation, guides, API reference
blogcontent/blog//blog/*Company blog, announcements, tutorials
pagescontent/pages//pages/*Static pages (about, terms, privacy)
releaseNotescontent/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 here

Extended 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
---

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

PatternDescriptionExample
"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-mdx

Adding 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/en

Create 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 typecheck

Internationalization (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:

  1. Create the directory: content/docs/fr/
  2. Copy meta.json and translate
  3. Create translated MDX files
  4. Update src/lib/i18n/config.ts if needed

Best Practices

Content Organization

  • One topic per page: Keep pages focused on a single concept
  • Use descriptive filenames: authentication.mdx not auth.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:generate

On this page