Documenting Eight Templates from One Codebase with fumadocs Multi-Root Docs

Eight products, eight full documentation sites, one repo. How we use fumadocs multi-root docs to keep them separate but unified.

SoarStarter Team

We sell eight templates, and each one needs real documentation — setup, architecture, auth, payments, deployment, the works. That's eight distinct doc sites: the Next.js docs shouldn't show Swift's navigation, and a reader on the Tauri docs shouldn't wander into Kotlin's pages by accident. But maintaining eight separate documentation apps would be its own full-time job.

The site you're reading solves this with fumadocs multi-root docs: eight independent documentation trees, each with its own sidebar and navigation, all living in one Next.js app and one content directory. Here's how it's wired, since it's a genuinely nice pattern for anyone documenting a family of related products.

One collection, many roots

All docs are a single fumadocs collection pointed at one directory:

// source.config.ts
export const docs = defineDocs({ dir: "content/docs" });

The multi-root magic is in the folder structure, not the config. Each template gets a top-level folder under content/docs/, and each of those folders declares itself a root in its meta.json:

// content/docs/next/meta.json
{
  "title": "Next.js",
  "icon": "Nextjs",
  "root": true,
  "pages": ["---Getting Started---", "index", "installation", ...]
}

That single "root": true line is what makes fumadocs treat the folder as a self-contained documentation tree. Inside a root, navigation, sidebar, and page tree are scoped to that folder — so the Next.js docs and the Swift docs are as isolated as if they were separate sites, while sharing one build, one search index, one theme, and one deploy.

Mapping roots to brand icons

Each root names an icon in its meta ("Nextjs", "Swift", "Tauri"…). fumadocs' source loader lets you resolve those names to components, so we map each to its real framework logo instead of a generic doc icon:

// src/lib/source.ts
export const source = loader({
  baseUrl: "/docs",
  source: docs.toFumadocsSource(),
  icon(icon) {
    const framework = frameworkIcons[icon];      // Nextjs, Nuxt, Swift, ...
    if (framework) return createElement(/* brand logo */);
    if (icon && icon in lucideIcons) return createElement(lucideIcons[icon]);
  },
});

Framework names hit our custom logo set; anything else falls through to lucide. The result is a root switcher where each template is recognizable by its actual logo.

Two gotchas worth passing on

Building this surfaced two things that cost us time, so here they are for the next person:

Navbar-tab icons must be self-contained. In the notebook layout, the root-switcher tabs render your icons in a context where SVG <defs> (shared gradients, filters referenced by id) don't resolve — the icon comes out blank. The fix is to keep those specific icons text- or path-only, with no defs-referenced fills. Full-color logos are fine in the sidebar; the tab rendering is the constrained spot.

Turbopack can serve stale docs CSS. During development, editing docs styling occasionally leaves Turbopack serving a cached stylesheet, so your change appears to do nothing. A clean restart of the dev server clears it — worth knowing before you debug a CSS change that was actually correct all along.

Localized on top of all of it

The docs are also bilingual (English and Chinese), and the i18n config has one detail worth calling out — the locale prefixing has to match the rest of the site:

i18n: {
  defaultLanguage: "en",
  languages: ["en", "zh"],
  // Match next-intl's `localePrefix: "as-needed"`: the default locale gets no
  // prefix, so generated doc URLs line up with the actual routes.
}

Get that wrong and fumadocs generates /en/docs/... links while your routes live at /docs/..., and every doc link 404s for the default locale. Matching fumadocs' i18n to next-intl's as-needed prefix keeps the URLs consistent.

Why this pattern is worth it

The payoff is leverage. Adding a ninth template's docs someday would be a new folder with "root": true and its pages — no new app, no new deploy target, no duplicated search or theming. Eight products stay cleanly separated for readers while collapsing into one maintainable codebase for us. If you're documenting a suite of related things rather than a single product, fumadocs multi-root is the pattern to reach for. You can see the result across all eight roots starting from the docs home.