magic-docs
v1.0.5
Published
Reusable Fumadocs foundation: site contract, layout and MDX presets, TypeScript API docs, static export helpers, and visual theme.
Maintainers
Readme
How it works
defineMagicDocsvalidates one site contract (name, URLs, package) at config evaluation time, and every helper below derives from it.- Presets style Fumadocs with the shared theme. Reference tables and llms.txt output generate at build time.
createMagicDocsStaticExportturns the app into a static Next export that GitHub Pages serves under a project path.
// lib/site.ts
import { createMagicDocsPublicPaths, defineMagicDocs } from "magic-docs";
export const site = defineMagicDocs({
name: "React Native Magic Modal",
description: "An imperative, promise-based modal library for React Native.",
siteUrl: "https://gstj.github.io/react-native-magic-modal",
repository: "https://github.com/GSTJ/react-native-magic-modal",
packageName: "react-native-magic-modal",
});
export const publicPaths = createMagicDocsPublicPaths(site);This is intentionally a preset. Content stays in each package repository; the preset centralizes the parts that should not drift. Packages still own their examples, guides, API decisions, and deploy workflow.
Install
Install the framework packages in the docs application:
pnpm add next@16 react react-dom fumadocs-core fumadocs-mdx \
fumadocs-ui@npm:@fumadocs/base-ui magic-docs
pnpm add -D typescript tailwindcss @tailwindcss/postcssThe fumadocs-ui alias selects Base UI while preserving Fumadocs' documented import paths.
Pin TypeScript separately from this monorepo's root: the docs app needs a compiler release
Next and Fumadocs have already caught up to. Exact ranges for everything else live in this
package's peerDependencies.
Site contract
The defineMagicDocs call above is the whole contract: define the package once and reuse the
result everywhere. Invalid or relative URLs fail during config evaluation. siteUrl includes the
GitHub Pages project path; that path is the source of truth for every public URL below.
Theme
For package consumption:
/* app/global.css */
@import "tailwindcss";
@import "magic-docs/theme.css";To keep a repository-owned snapshot instead:
pnpm exec magic-docs-init --out app/magic-docs.cssThe command is idempotent, refuses to overwrite a customized copy without --force, and also
creates public/.nojekyll. Import the copied file after Tailwind:
@import "tailwindcss";
@import "./magic-docs.css";A package can override the font variables without forking the theme:
:root {
--font-magic-sans: var(--font-geist-sans);
--font-magic-mono: var(--font-geist-mono);
}Layout and MDX
Keep the classic docs layout: it has persistent navigation, search, and a table of contents.
// app/(docs)/layout.tsx
import { createMagicDocsLayout } from "magic-docs/fumadocs";
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import { site } from "@/lib/site";
import { source } from "@/lib/source";
export default function Layout({ children }: LayoutProps<"/">) {
return (
<DocsLayout {...createMagicDocsLayout(site)} tree={source.getPageTree()}>
{children}
</DocsLayout>
);
}createMagicDocsLayout keeps those controls in sync across packages. Add package-specific links
through its links option.
The shared MDX vocabulary includes Fumadocs' default cards, callouts, headings, and code blocks plus accordions, file trees, steps, tabs, and type tables:
// mdx-components.tsx
import { createMagicDocsMdxComponents } from "magic-docs/mdx";
export const getMDXComponents = createMagicDocsMdxComponents;
export const useMDXComponents = createMagicDocsMdxComponents;Type tables
Use build-time generation. Paths stay relative to the MDX file and static export does not need the TypeScript filesystem at runtime.
// source.config.ts
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
import { magicDocsLlmMdxOptions } from "magic-docs/llms";
import { createMagicDocsTypeScript } from "magic-docs/typescript";
const typescript = createMagicDocsTypeScript({
cacheDirectory: ".next/fumadocs-typescript",
});
export const docs = defineDocs({
dir: "content/docs",
docs: {
postprocess: {
includeProcessedMarkdown: magicDocsLlmMdxOptions,
},
},
});
export default defineConfig({
mdxOptions: {
remarkPlugins: [typescript.remarkPlugin],
},
});Then reference a public type:
<auto-type-table path="../../../src/types.ts" name="ModalProps" />Generated tables supplement prose. fumadocs-typescript renders object and interface
properties well, but:
- top-level functions generate no useful entries;
- enums can expose inherited
Stringprototype members; - property
@descriptiontags do not become the table summary.
Document functions and enums manually, and use normal JSDoc prose on object properties. Keep generated reference pages behind task-first quickstarts and guides.
Agent-readable docs
magicDocsLlmMdxOptions is not optional when generated TypeTables are present. Without its
TypeTable placeholder, Fumadocs serializes the generated prop's full JSON/ESTree into processed
Markdown.
Use the shared renderer for page Markdown and llms-full.txt:
import { createMagicDocsLlmPage } from "magic-docs/llms";
import { site } from "@/lib/site";
export async function getLlmText(page: (typeof source)["$inferPage"]) {
return createMagicDocsLlmPage(site, {
title: page.data.title,
description: page.data.description,
url: page.url,
processedMarkdown: await page.data.getText("processed"),
});
}It renders each prop as a Markdown row with its type and default.
Fumadocs' llms(source).index() emits application-relative links. Prefix them before returning
llms.txt:
import { llms } from "fumadocs-core/source";
import { prefixMagicDocsLlmLinks } from "magic-docs/llms";
export function GET() {
return new Response(prefixMagicDocsLlmLinks(site, llms(source).index()));
}Static export on GitHub Pages
The portable Next config is:
// next.config.ts
import type { NextConfig } from "next";
import { createMDX } from "fumadocs-mdx/next";
import { createMagicDocsStaticExport } from "magic-docs";
import { site } from "./lib/site";
const withMdx = createMDX();
const config = createMagicDocsStaticExport(site) satisfies NextConfig;
export default withMdx(config);It configures the static-export settings GitHub Pages needs. It deliberately does not set
assetPrefix: Next handles /_next assets from basePath and does not recommend assetPrefix
for sub-path hosting.
basePath does not rewrite fetch URLs or strings. Use the shared paths for the places outside
the Next router:
import { oramaStaticClient } from "fumadocs-core/search/client/orama-static";
import { publicPaths } from "@/lib/site";
const search = oramaStaticClient({ from: publicPaths.searchApi });
publicPaths.markdown(page.url); // copy-Markdown button URL
publicPaths.llms; // /<project>/llms.txt
publicPaths.llmsFull; // /<project>/llms-full.txt
publicPaths.url(page.url); // canonical/OG absolute URLStatic search also needs a static route:
export const revalidate = false;
export const { staticGET: GET } = createFromSource(source);The Pages workflow must preserve hidden files:
- uses: actions/upload-pages-artifact@v4
with:
path: out
include-hidden-files: trueWithout include-hidden-files, the generated public/.nojekyll disappears during upload and
GitHub may process the export with Jekyll.
Adoption before the first npm release
Create a real package tarball:
# in GSTJ/magic
pnpm --filter magic-docs pack --pack-destination ./artifactsFor a full pre-release adoption, copy the tarball into the consumer:
vendor/magic-docs-<version>.tgzand install that committed artifact:
pnpm add ./vendor/magic-docs-<version>.tgz
pnpm exec magic-docs-init --out app/magic-docs.cssThe frozen lockfile then depends only on a file committed in the consumer. After the first
publish, replace the
file:vendor/...tgz spec with the npm version; none of the imports or config change.
For a theme-only bootstrap, run the CLI from the tarball, commit the generated CSS and
.nojekyll, and do not retain magic-docs as a dependency until it is published.
pnpm dlx ./artifacts/magic-docs-<version>.tgz \
--out app/magic-docs.cssContent standard
Every package starts with this order:
- Overview and a concrete result.
- Install.
- Five-minute quickstart.
- Task-oriented guides and recipes.
- API reference.
- Troubleshooting and migration notes.
Reference pages are generated from source. Each public feature needs at least one runnable example and a plain-language explanation of when to use it.
