mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
Docs: Document new MDX APIs
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
---
|
||||
'fumadocs-mdx': major
|
||||
---
|
||||
|
||||
**Support declarative collections**
|
||||
|
||||
**why:** This allows Fumadocs MDX to be more flexible.
|
||||
|
||||
**migrate:**
|
||||
|
||||
You don't need `exports` anymore, properties are merged into one object by default.
|
||||
|
||||
```diff
|
||||
- page.data.exports.toc
|
||||
+ page.data.toc
|
||||
|
||||
- page.data.exports.default
|
||||
+ page.data.body
|
||||
```
|
||||
|
||||
A `source.config.ts` is now required.
|
||||
|
||||
```ts
|
||||
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
|
||||
|
||||
export const { docs, meta } = defineDocs();
|
||||
|
||||
export default defineConfig();
|
||||
```
|
||||
|
||||
The `mdx-components.tsx` file is no longer used, pass MDX components to body instead.
|
||||
|
||||
Search indexes API is now replaced by Manifest API.
|
||||
|
||||
Please refer to the docs for further details.
|
||||
@@ -54,7 +54,7 @@ sync(client, {
|
||||
});
|
||||
```
|
||||
|
||||
The `indexes` variable should be provided by your content source provider. If you are using Fumadocs MDX, see [Search Index Generation](/docs/mdx/search-index).
|
||||
The `indexes` variable should be provided by your content source provider. If you are using Fumadocs MDX, see [Manifest](/docs/mdx/manifest).
|
||||
|
||||
Notably, it requires a `structured` property, this can be obtained from [Remark Structure](/docs/headless/mdx/structure), or sometimes supplied by the content source you are using.
|
||||
|
||||
|
||||
@@ -4,49 +4,164 @@ description: Learn to configure Fumadocs MDX
|
||||
icon: Paperclip
|
||||
---
|
||||
|
||||
## Plugin Options
|
||||
## Configuration File
|
||||
|
||||
Fumadocs MDX offers webpack plugins and a Fumadocs [Source API](/docs/headless/source-api) adapter to integrate with Fumadocs.
|
||||
You can configure Fumadocs MDX by passing options to `createMDX` in `next.config.mjs`.
|
||||
Configure Fumadocs MDX by creating a `source.config.ts` file.
|
||||
|
||||
```ts
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
### Global Options
|
||||
|
||||
const withMDX = createMDX({
|
||||
// options
|
||||
Shared options of Fumadocs MDX.
|
||||
|
||||
```ts title="source.config.ts"
|
||||
import { defineConfig } from 'fumadocs-mdx/config';
|
||||
|
||||
export default defineConfig({
|
||||
// global options
|
||||
});
|
||||
```
|
||||
|
||||
### Root Content Directory
|
||||
```json doc-gen:typescript
|
||||
{
|
||||
"file": "./content/docs/mdx/props.ts",
|
||||
"name": "GlobalConfig"
|
||||
}
|
||||
```
|
||||
|
||||
Fumadocs MDX only scans for a specific folder to produce the `.map` file, it's called the root content directory.
|
||||
#### MDX Options
|
||||
|
||||
By default, it points to `./content` in the root directory. You can customise it with the `rootContentDir` option.
|
||||
Customise the default MDX options of all MDX files.
|
||||
|
||||
### Map File Path
|
||||
See [Default MDX Options](#default-mdx-options) for more details.
|
||||
|
||||
Customise the file path of `.map` file with the `rootMapPath` option.
|
||||
#### Manifest
|
||||
|
||||
### Include
|
||||
Enable the `generateManifest` option to generate a manifest file for production builds.
|
||||
|
||||
Include or filter files to the `.map` file, it uses micromatch under the hood which supports Regex.
|
||||
See [Manifest](/docs/mdx/manifest) to learn more.
|
||||
|
||||
### Collections
|
||||
|
||||
Define a collection to parse a certain set of files.
|
||||
|
||||
```ts
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
import { defineCollections } from 'fumadocs-mdx/config';
|
||||
import { z } from 'zod';
|
||||
|
||||
const withMDX = createMDX({
|
||||
include: ['./**/*.{md,mdx,json}'],
|
||||
export const blog = defineCollections({
|
||||
type: 'doc',
|
||||
dir: './content/blog',
|
||||
schema: z.object({
|
||||
// allowed...
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
### Current Working Directory
|
||||
```json doc-gen:typescript
|
||||
{
|
||||
"file": "./content/docs/mdx/props.ts",
|
||||
"name": "Collections"
|
||||
}
|
||||
```
|
||||
|
||||
Customise the current working directory with the `cwd` option, default to `process.cwd()`.
|
||||
#### Schema
|
||||
|
||||
## MDX Options
|
||||
The Zod schema to validate file data (frontmatter on `doc` type, content on `meta` type).
|
||||
|
||||
```ts
|
||||
import { defineCollections } from 'fumadocs-mdx/config';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const blog = defineCollections({
|
||||
type: 'doc',
|
||||
dir: './content/blog',
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
You can add additional properties to the output.
|
||||
Note that the validation is done by build time, hence the output must be serializable.
|
||||
|
||||
You can also pass a function and receives the transform context.
|
||||
|
||||
```ts
|
||||
import { defineCollections } from 'fumadocs-mdx/config';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const blog = defineCollections({
|
||||
type: 'doc',
|
||||
dir: './content/blog',
|
||||
schema: (ctx) => {
|
||||
return z.object({
|
||||
name: z.string(),
|
||||
testPath: z.string().default(ctx.path),
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Type
|
||||
|
||||
The accepted type of collection.
|
||||
|
||||
| Type | Description |
|
||||
| ------ | ---------------------- |
|
||||
| `meta` | JSON/YAML File |
|
||||
| `doc` | Markdown/MDX Documents |
|
||||
|
||||
#### MDX Options
|
||||
|
||||
You can also customise MDX options from collections.
|
||||
Notice that passing `mdxOptions` to collection overrides all defaults from global config.
|
||||
|
||||
```ts
|
||||
import { defineCollections, getDefaultMDXOptions } from 'fumadocs-mdx/config';
|
||||
|
||||
export const blog = defineCollections({
|
||||
type: 'doc',
|
||||
mdxOptions: getDefaultMDXOptions({
|
||||
// mdx options
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
We use `getDefaultMDXOptions` to apply default MDX options, it accepts the [Default MDX Options](#default-mdx-options).
|
||||
|
||||
For full control over MDX options, you can pass MDX options without `getDefaultMDXOptions`, which means no defaults will be applied (except the ones from MDX.js).
|
||||
|
||||
> This API only available on `doc` type.
|
||||
|
||||
#### Transform
|
||||
|
||||
A function to perform runtime transformation on collection entries.
|
||||
|
||||
See [Transform](/docs/mdx/transform).
|
||||
|
||||
### Define Docs
|
||||
|
||||
You can use `defineDocs` to define the required collections to work with Fumadocs.
|
||||
It offers the same API as `defineCollections`.
|
||||
|
||||
```ts
|
||||
import { defineDocs } from 'fumadocs-mdx/config';
|
||||
|
||||
export const { docs, meta } = defineDocs({
|
||||
docs: {
|
||||
// optional, you can pass options to each collection
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Default MDX Options
|
||||
|
||||
Fumadocs MDX uses the [MDX Compiler](https://mdxjs.com/packages/mdx) to compile MDX files into JavaScript files.
|
||||
You can configure the compiler with the `mdxOptions` option.
|
||||
|
||||
Since some options should be applied by default, it exposes a `getDefaultMDXOptions` function that applies these defaults on the input MDX options.
|
||||
|
||||
To allow changing the default settings, it supports a different set of options.
|
||||
|
||||
> MDX options from global config are always applied automatically.
|
||||
|
||||
### Remark Plugins
|
||||
|
||||
@@ -55,31 +170,27 @@ These plugins are applied by default:
|
||||
- [Remark Image](/docs/headless/mdx/remark-image) - Handle images
|
||||
- [Remark Heading](/docs/headless/mdx/toc) - Extract table of contents
|
||||
- [Remark Structure](/docs/headless/mdx/structure) - Generate search indexes
|
||||
- Custom Remark Plugin - Exports the output generated by remark plugins above
|
||||
- Remark Exports - Exports the output generated by remark plugins above
|
||||
|
||||
You can add other remark plugins with:
|
||||
|
||||
```ts
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
import { getDefaultMDXOptions } from 'fumadocs-mdx/config';
|
||||
import { myPlugin } from './remark-plugin';
|
||||
|
||||
const withMDX = createMDX({
|
||||
mdxOptions: {
|
||||
remarkPlugins: [myPlugin],
|
||||
},
|
||||
getDefaultMDXOptions({
|
||||
remarkPlugins: [myPlugin],
|
||||
});
|
||||
```
|
||||
|
||||
You can also pass a function to control the order of remark plugins.
|
||||
|
||||
```ts
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
import { getDefaultMDXOptions } from 'fumadocs-mdx/config';
|
||||
import { myPlugin } from './remark-plugin';
|
||||
|
||||
const withMDX = createMDX({
|
||||
mdxOptions: {
|
||||
remarkPlugins: (v) => [myPlugin, ...v],
|
||||
},
|
||||
getDefaultMDXOptions({
|
||||
remarkPlugins: (v) => [myPlugin, ...v],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -92,26 +203,24 @@ These plugins are applied by default:
|
||||
Same as remark plugins, you can pass an array or a function to add other rehype plugins.
|
||||
|
||||
```ts
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
import { getDefaultMDXOptions } from 'fumadocs-mdx/config';
|
||||
import { myPlugin } from './rehype-plugin';
|
||||
|
||||
const withMDX = createMDX({
|
||||
mdxOptions: {
|
||||
rehypePlugins: (v) => [myPlugin, ...v],
|
||||
},
|
||||
getDefaultMDXOptions({
|
||||
rehypePlugins: (v) => [myPlugin, ...v],
|
||||
});
|
||||
```
|
||||
|
||||
### Rehype Code
|
||||
### Customise Built-in Plugins
|
||||
|
||||
Customise the options for Rehype Code and [Shiki](https://shiki.style).
|
||||
Customise the options of built-in plugins.
|
||||
|
||||
```ts
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
import { getDefaultMDXOptions } from 'fumadocs-mdx/config';
|
||||
|
||||
const withMDX = createMDX({
|
||||
mdxOptions: {
|
||||
rehypeCode: { ... }
|
||||
getDefaultMDXOptions({
|
||||
rehypeCodeOptions: {
|
||||
// options
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -124,10 +233,10 @@ Fumadocs MDX applies a remark plugin that turns `vfile.data` properties into exp
|
||||
You can define additional properties to be exported.
|
||||
|
||||
```js
|
||||
const withMDX = createMDX({
|
||||
mdxOptions: {
|
||||
valueToExport: ['dataName'],
|
||||
},
|
||||
import { getDefaultMDXOptions } from 'fumadocs-mdx/config';
|
||||
|
||||
getDefaultMDXOptions({
|
||||
valueToExport: ['dataName'],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -137,8 +246,19 @@ By default, it includes:
|
||||
- `structuredData` for the Remark Structure Plugin
|
||||
- `frontmatter` for the frontmatter of MDX (using `gray-matter`)
|
||||
|
||||
## Search Index Options
|
||||
## Plugin Options
|
||||
|
||||
Pass the `buildSearchIndex` option to configure search index generation.
|
||||
Fumadocs MDX offers loaders and a Fumadocs [Source API](/docs/headless/source-api) adapter to integrate with Fumadocs.
|
||||
You can configure the plugin by passing options to `createMDX` in `next.config.mjs`.
|
||||
|
||||
See [Search Index Generation](/docs/mdx/search-index) to learn more.
|
||||
### Config Path
|
||||
|
||||
Customise the path of config file.
|
||||
|
||||
```ts
|
||||
import { createMDX } from 'fumadocs-mdx/next';
|
||||
|
||||
const withMDX = createMDX({
|
||||
configPath: './my-config.ts',
|
||||
});
|
||||
```
|
||||
|
||||
@@ -24,7 +24,7 @@ fumadocs-mdx @types/mdx
|
||||
Add the plugin to your `next.config.mjs` file.
|
||||
|
||||
```js
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
import { createMDX } from 'fumadocs-mdx/next';
|
||||
|
||||
const withMDX = createMDX();
|
||||
|
||||
@@ -36,8 +36,8 @@ const config = {
|
||||
export default withMDX(config);
|
||||
```
|
||||
|
||||
It generates a `.map.ts` file once you start the dev server or start building the app.
|
||||
The map file contains all parsed files, you should add it to `.gitignore`.
|
||||
It generates a `.source` folder once you start the dev server or start building the app.
|
||||
The folder contains all parsed files, you should add it to `.gitignore`.
|
||||
|
||||
<Callout title="ESM Only" type='warn' className="mt-4">
|
||||
|
||||
@@ -45,20 +45,23 @@ The Next.js config must be a `.mjs` file since Fumadocs is ESM-only.
|
||||
|
||||
</Callout>
|
||||
|
||||
### Define MDX Components
|
||||
To configure Fumadocs MDX, create a config file.
|
||||
|
||||
Create `mdx-components.tsx` in your root directory.
|
||||
```ts title="source.config.ts"
|
||||
import { defineDocs } from 'fumadocs-mdx/config';
|
||||
|
||||
```tsx
|
||||
import type { MDXComponents } from 'mdx/types';
|
||||
// Assume you're using Fumadocs UI
|
||||
import defaultComponents from 'fumadocs-ui/mdx';
|
||||
export const { docs, meta } = defineDocs();
|
||||
```
|
||||
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
...defaultComponents,
|
||||
...components,
|
||||
};
|
||||
### Post Install
|
||||
|
||||
You can add a post install script to generate types before executing CLI tools (e.g. ESLint).
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"postinstall": "fumadocs-mdx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -67,14 +70,13 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
To integrate with Fumadocs, create:
|
||||
|
||||
```ts title="app/source.ts"
|
||||
import { map } from '@/.map';
|
||||
import { docs, meta } from '@/.source';
|
||||
import { createMDXSource } from 'fumadocs-mdx';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
|
||||
export const { getPage, getPages, pageTree } = loader({
|
||||
baseUrl: '/docs',
|
||||
rootDir: 'docs',
|
||||
source: createMDXSource(map),
|
||||
source: createMDXSource(docs, meta),
|
||||
});
|
||||
```
|
||||
|
||||
@@ -86,11 +88,50 @@ export const { getPage, getPages, pageTree } = loader({
|
||||
next dev
|
||||
```
|
||||
|
||||
A `.map.ts` file should be created. You can log and see if it is loaded correctly.
|
||||
A `.source` folder should be created. You can log and see if it is loaded correctly.
|
||||
|
||||
See [Pages Conventions](/docs/ui/page-conventions)
|
||||
to learn how to organize your pages.
|
||||
|
||||
### Usages
|
||||
|
||||
Usually, you'll interact with Fumadocs MDX through Source API (the `loader`).
|
||||
You can see the type definitions to find the available properties.
|
||||
|
||||
To render the page, pass components to the body.
|
||||
|
||||
```tsx title="page.tsx (Fumadocs UI)"
|
||||
import { getPage } from '@/app/source';
|
||||
import {
|
||||
DocsPage,
|
||||
DocsBody,
|
||||
DocsTitle,
|
||||
DocsDescription,
|
||||
} from 'fumadocs-ui/page';
|
||||
import { notFound } from 'next/navigation';
|
||||
import defaultComponents from 'fumadocs-ui/mdx';
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: { slug?: string[] };
|
||||
}) {
|
||||
const page = getPage(params.slug);
|
||||
|
||||
if (!page) notFound();
|
||||
|
||||
return (
|
||||
<DocsPage toc={page.data.toc} full={page.data.full}>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<page.data.body components={defaultComponents} />
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### Built-in Properties
|
||||
@@ -107,12 +148,12 @@ These properties are exported from MDX files by default.
|
||||
|
||||
You can customise the options passed to the MDX processor.
|
||||
|
||||
```js
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
```ts title="source.config.ts"
|
||||
import { defineConfig } from 'fumadocs-mdx/config';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import remarkMath from 'remark-math';
|
||||
|
||||
const withMDX = createMDX({
|
||||
export default defineConfig({
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkMath],
|
||||
// When order matters
|
||||
@@ -122,18 +163,3 @@ const withMDX = createMDX({
|
||||
```
|
||||
|
||||
See [Configuration](/docs/mdx/configuration) to learn more.
|
||||
|
||||
### What is the `.map` file?
|
||||
|
||||
The `.map` file is generated by our Webpack plugin in build time.
|
||||
It includes the information of files under your content directory,
|
||||
which can be access by your Next.js app to implement SSG and many other features.
|
||||
|
||||
## Deep Dive
|
||||
|
||||
### How does it work?
|
||||
|
||||
Fumadocs MDX is a webpack plugin over Next.js to support reading/importing a MDX file by its name.
|
||||
Because Next.js could only optimize static imports, it first generates a map file that imports **all supported files** under the root directory.
|
||||
|
||||
The imported files form an array called `map` in your `.map.ts` file, so that your Next.js app can access them without manually importing all of them.
|
||||
|
||||
@@ -5,16 +5,14 @@ description: Output the last modified time of a document
|
||||
|
||||
## Usage
|
||||
|
||||
This feature is not enabled by default, you can enable this in `next.config.mjs`. Notice that it only supports Git as version control.
|
||||
When using `git`, please ensure you have Git installed on your local machine and the repository is not shallow cloned as it relies on your local Git history.
|
||||
This feature is not enabled by default, you can enable this from config file. Notice that it only supports Git as version control.
|
||||
When using `git`, please ensure you have Git installed on your local machine and the repository is not shallow cloned, as it relies on your local Git history.
|
||||
|
||||
```js
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
import { defineConfig } from 'fumadocs-mdx/config';
|
||||
|
||||
const withMDX = createMDX({
|
||||
mdxOptions: {
|
||||
lastModifiedTime: 'git', // [!code highlight]
|
||||
},
|
||||
export default defineConfig({
|
||||
lastModifiedTime: 'git', // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
@@ -27,5 +25,5 @@ import { getPage } from '@/app/source';
|
||||
|
||||
const page = getPage('...');
|
||||
|
||||
console.log(new Date(page.data.exports.lastModified));
|
||||
console.log(new Date(page.data.lastModified));
|
||||
```
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Manifest
|
||||
description: Generate manifest for production builds
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Enable it from global config.
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'fumadocs-mdx/config';
|
||||
|
||||
export default defineConfig({
|
||||
generateManifest: true,
|
||||
});
|
||||
```
|
||||
|
||||
During a production build (`next build`), Fumadocs MDX will output a manifest file in `.source/manifest.json`.
|
||||
You can use it to set up search indexes, like configuring Algolia Search.
|
||||
|
||||
### What is It?
|
||||
|
||||
The manifest file contains the compiled info of collection entries.
|
||||
This includes their frontmatter, `structuredData` and `toc` (Table of Contents).
|
||||
|
||||
You can check the type definitions to see all available properties.
|
||||
|
||||
```ts
|
||||
import type { Manifest } from 'fumadocs-mdx';
|
||||
```
|
||||
|
||||
### Read Manifest
|
||||
|
||||
You can read the file after the build is finished.
|
||||
|
||||
```ts
|
||||
import type { Manifest } from 'fumadocs-mdx';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const manifest = JSON.parse(
|
||||
readFileSync('.source/manifest.json').toString(),
|
||||
) as Manifest;
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
To integrate with Algolia Search:
|
||||
|
||||
```ts title="update-indexes.ts"
|
||||
import algosearch from 'algoliasearch';
|
||||
import { sync } from 'fumadocs-core/search-algolia/server';
|
||||
import type { Manifest } from 'fumadocs-mdx';
|
||||
import { createGetUrl, getSlugs, parseFilePath } from 'fumadocs-core/source';
|
||||
import path from 'node:path';
|
||||
|
||||
const getUrl = createGetUrl('/docs'); // we need to estimate the URL of pages
|
||||
|
||||
export async function updateSearchIndexes(manifest: Manifest): Promise<void> {
|
||||
const client = algosearch(
|
||||
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
|
||||
process.env.ALGOLIA_API_KEY,
|
||||
);
|
||||
|
||||
await sync(client, {
|
||||
document: process.env.NEXT_PUBLIC_ALGOLIA_INDEX ?? 'document',
|
||||
documents: manifest.files
|
||||
.filter((file) => file.collection === 'docs') // the collection name
|
||||
.map((docs) => {
|
||||
const url = getUrl(
|
||||
getSlugs(parseFilePath(path.relative('content/docs', docs.path))),
|
||||
);
|
||||
|
||||
if (!docs.data.structuredData)
|
||||
throw new Error('`structuredData` is required');
|
||||
|
||||
return {
|
||||
_id: docs.path,
|
||||
title: docs.data.frontmatter.title as string,
|
||||
description: docs.data.frontmatter.description as string,
|
||||
url,
|
||||
structured: docs.data.structuredData,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
> `frontmatter` is untyped. With type check enabled, you need to cast them to the correct type.
|
||||
@@ -8,9 +8,7 @@
|
||||
"---Features---",
|
||||
"last-modified",
|
||||
"image",
|
||||
"search-index",
|
||||
"---Schema---",
|
||||
"validation",
|
||||
"multiple-types"
|
||||
"manifest",
|
||||
"transform"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
title: Multiple Types
|
||||
description: Multiple content types
|
||||
---
|
||||
|
||||
You can have multiple document types, such as blog posts, or documents.
|
||||
|
||||
Use the `rootDir` option to restrict the folder it scans.
|
||||
|
||||
```ts
|
||||
import { map } from '@/.map';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
import { createMDXSource } from 'fumadocs-mdx';
|
||||
|
||||
export const docs = loader({
|
||||
baseUrl: '/docs',
|
||||
rootDir: 'docs',
|
||||
source: createMDXSource(map),
|
||||
});
|
||||
|
||||
export const blogs = loader({
|
||||
baseUrl: '/blog',
|
||||
rootDir: 'blog',
|
||||
source: createMDXSource(map),
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
export type { GlobalConfig, Collections } from 'fumadocs-mdx/config';
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Transform
|
||||
description: Runtime Transformation on collection entries
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Fumadocs MDX bundles collection entries during build time, but this introduced some limitations on the configurations, forcing the output must be serializable.
|
||||
|
||||
You can use `transform` to transform collection entries on Next.js runtime.
|
||||
It includes extra benefits:
|
||||
|
||||
- Unserializable output (e.g. functions)
|
||||
- Server/Client Components
|
||||
- Reference other parts of your Next.js apps
|
||||
|
||||
### How does it works?
|
||||
|
||||
This is possible by performing a transformation again on the bundled collection entries.
|
||||
Everything is done on Next.js runtime, so using server/client components on `transform` is possible.
|
||||
|
||||
Be careful that:
|
||||
|
||||
- **Not cached by bundlers:** avoid expensive calculations in `transform`.
|
||||
- **Use async imports:** Fumadocs MDX has to bundle the config file with esbuild before loading it. If importing a specific file results in errors, consider to use async import (`await import()`) instead.
|
||||
|
||||
### Usage
|
||||
|
||||
Pass a transform function.
|
||||
|
||||
```ts
|
||||
import { defineCollections } from 'fumadocs-mdx/config';
|
||||
|
||||
export const blog = defineCollections({
|
||||
type: 'doc',
|
||||
transform: (entry) => {
|
||||
return {
|
||||
...entry,
|
||||
test: 'hello world',
|
||||
};
|
||||
},
|
||||
// other props
|
||||
});
|
||||
```
|
||||
|
||||
You can also use imports inside the function.
|
||||
|
||||
```ts
|
||||
import React from 'react';
|
||||
import { defineCollections } from 'fumadocs-mdx/config';
|
||||
|
||||
export const blog = defineCollections({
|
||||
type: 'doc',
|
||||
transform: async (entry) => {
|
||||
const { Component } = await import('./my-server-component');
|
||||
return {
|
||||
...entry,
|
||||
test: React.createElement(Component, {
|
||||
// props
|
||||
}),
|
||||
};
|
||||
},
|
||||
// other props
|
||||
});
|
||||
```
|
||||
|
||||
### Build-time Transform
|
||||
|
||||
To transform collection entries during build time, use [Schema](/docs/mdx/configuration#schema) with [Zod `transform`](https://zod.dev/?id=transform) instead.
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
title: Validation
|
||||
description: Validate your files via Zod
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
The file validation is based on [Zod](https://zod.dev). When an invalid file is
|
||||
detected, it will throw an error immediately.
|
||||
|
||||
To customise the schemas, you have to install `zod`.
|
||||
|
||||
```package-install
|
||||
zod
|
||||
```
|
||||
|
||||
Now you can override or extend the default zod schemas with the `schema`
|
||||
option.
|
||||
|
||||
### Example
|
||||
|
||||
Extend the default frontmatter schema:
|
||||
|
||||
```ts title="app/source.ts"
|
||||
import { createMDXSource, defaultSchemas } from 'fumadocs-mdx';
|
||||
import { z } from 'zod';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
import { map } from '@/.map';
|
||||
|
||||
const frontmatterSchema = defaultSchemas.frontmatter.extend({
|
||||
preview: z.string().optional(),
|
||||
});
|
||||
|
||||
export const utils = loader({
|
||||
baseUrl: '/docs',
|
||||
rootDir: 'docs',
|
||||
source: createMDXSource(map, { schema: { frontmatter: frontmatterSchema } }),
|
||||
});
|
||||
```
|
||||
|
||||
### Typescript
|
||||
|
||||
Thanks to the Typescript magic, your custom Zod schema type should be automatically inferred.
|
||||
|
||||
```ts title="source.ts"
|
||||
utils.getPages().data; // should be your own frontmatter
|
||||
```
|
||||
|
||||
Infer the type of pages and meta files with build-in type helpers of Source API.
|
||||
|
||||
```ts
|
||||
import type { InferMetaType, InferPageType } from 'fumadocs-core/source';
|
||||
|
||||
export type Page = InferPageType<typeof utils>;
|
||||
export type Meta = InferMetaType<typeof utils>;
|
||||
```
|
||||
@@ -1,11 +1,13 @@
|
||||
import { getPage, getPages } from '@/app/source';
|
||||
import type { Metadata } from 'next';
|
||||
import { DocsPage, DocsBody, DocsTitle } from 'fumadocs-ui/page';
|
||||
import {
|
||||
DocsPage,
|
||||
DocsBody,
|
||||
DocsTitle,
|
||||
DocsDescription,
|
||||
} from 'fumadocs-ui/page';
|
||||
import { notFound } from 'next/navigation';
|
||||
import defaultComponents from 'fumadocs-ui/mdx';
|
||||
import { toJsxRuntime } from 'hast-util-to-jsx-runtime';
|
||||
import { Fragment } from 'react';
|
||||
import { jsx, jsxs } from 'react/jsx-runtime';
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
@@ -14,29 +16,13 @@ export default async function Page({
|
||||
}) {
|
||||
const page = getPage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const descriptionMd = toJsxRuntime(page.data.descriptionHast, {
|
||||
development: false,
|
||||
Fragment,
|
||||
// @ts-expect-error -- jsx
|
||||
jsx,
|
||||
// @ts-expect-error -- jsx
|
||||
jsxs,
|
||||
// @ts-expect-error -- jsx
|
||||
components: defaultComponents,
|
||||
});
|
||||
if (!page) notFound();
|
||||
|
||||
return (
|
||||
<DocsPage toc={page.data.toc} full={page.data.full}>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<div className="mb-8 text-lg text-fd-muted-foreground">
|
||||
{descriptionMd}
|
||||
</div>
|
||||
<page.data.body components={defaultComponents} />
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
|
||||
@@ -1,50 +1,5 @@
|
||||
import {
|
||||
defineDocs,
|
||||
defineCollections,
|
||||
frontmatterSchema,
|
||||
defineConfig,
|
||||
} from 'fumadocs-mdx/config';
|
||||
import { z } from 'zod';
|
||||
import { remark } from 'remark';
|
||||
import {
|
||||
rehypeCode,
|
||||
remarkGfm,
|
||||
remarkHeading,
|
||||
remarkImage,
|
||||
} from 'fumadocs-core/mdx-plugins';
|
||||
import remarkRehype from 'remark-rehype';
|
||||
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
|
||||
|
||||
const processor = remark()
|
||||
.use(remarkGfm)
|
||||
.use(remarkImage, { useImport: false })
|
||||
.use(remarkHeading)
|
||||
.use(remarkRehype)
|
||||
.use(rehypeCode);
|
||||
export const { docs, meta } = defineDocs();
|
||||
|
||||
export const { docs, meta } = defineDocs({
|
||||
docs: {
|
||||
schema: frontmatterSchema.transform(async (v) => {
|
||||
const parsed = processor.parse(v.description);
|
||||
const hast = await processor.run(parsed);
|
||||
|
||||
return {
|
||||
...v,
|
||||
descriptionHast: hast,
|
||||
test: 'goodbye hi',
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
export const blog = defineCollections({
|
||||
type: 'doc',
|
||||
dir: './content/blog',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export default defineConfig({
|
||||
generateManifest: true,
|
||||
mdxOptions: {},
|
||||
});
|
||||
export default defineConfig();
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function generateStaticParams() {
|
||||
export function generateMetadata({ params }: { params: { slug?: string[] } }) {
|
||||
const page = getPage(params.slug);
|
||||
|
||||
if (page == null) notFound();
|
||||
if (!page) notFound();
|
||||
|
||||
return {
|
||||
title: page.data.title,
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface Collections<
|
||||
*/
|
||||
files?: string[];
|
||||
|
||||
schema: Schema | ((ctx: TransformContext) => Schema);
|
||||
schema?: Schema | ((ctx: TransformContext) => Schema);
|
||||
|
||||
/**
|
||||
* content type
|
||||
|
||||
Reference in New Issue
Block a user