diff --git a/apps/docs/app/(home)/page.tsx b/apps/docs/app/(home)/page.tsx
index 61efbe3b5..bf64252ee 100644
--- a/apps/docs/app/(home)/page.tsx
+++ b/apps/docs/app/(home)/page.tsx
@@ -445,11 +445,12 @@ function Hero(): React.ReactElement {
Build excellent documentation site with less effort
- Fumadocs is a beautiful{' '}
- documentation framework with{' '}
- a complete toolchain,
- powered by Next.js App Router.
- Designed to be flexible and fast.
+ Fumadocs is a{' '}
+ beautiful & robust{' '}
+ documentation framework with a complete toolchain. Designed for{' '}
+ Flexibility,{' '}
+ Performance and{' '}
+ Next.js.
-
-
-
{children}
Update the route handler (From Source)
-You don't need to change anything, it handles i18n automatically.
+Enable i18n from source object (in `loader` function).
Update the route handler (From Search Indexes)
diff --git a/apps/docs/content/docs/mdx/image.mdx b/apps/docs/content/docs/mdx/image.mdx
deleted file mode 100644
index 058542ecb..000000000
--- a/apps/docs/content/docs/mdx/image.mdx
+++ /dev/null
@@ -1,25 +0,0 @@
----
-title: Image Optimization
-description: Adding images to your docs
----
-
-Fumadocs MDX resolves images into static imports with [Remark Image](/docs/headless/mdx/remark-image).
-Therefore, your images will be optimized automatically by the Next.js Image API.
-
-```mdx
-![Hello](./hello.png)
-
-or in public folder
-
-![Hello](/hello.png)
-```
-
-Yields:
-
-```mdx
-import HelloImage from './hello.png';
-
-
-```
-
-![Banner](/banner.png)
diff --git a/apps/docs/content/docs/mdx/include.mdx b/apps/docs/content/docs/mdx/include.mdx
new file mode 100644
index 000000000..4d51b308a
--- /dev/null
+++ b/apps/docs/content/docs/mdx/include.mdx
@@ -0,0 +1,14 @@
+---
+title: Include
+description: Reuse content from other Markdown files
+---
+
+## Usage
+
+Specify the target Markdown file path in `` tag (relative).
+
+```mdx title="page.mdx"
+./another.mdx
+```
+
+This will display the content from target file (e.g. `another.mdx`).
diff --git a/apps/docs/content/docs/mdx/meta.json b/apps/docs/content/docs/mdx/meta.json
index 97583ffc3..18668684b 100644
--- a/apps/docs/content/docs/mdx/meta.json
+++ b/apps/docs/content/docs/mdx/meta.json
@@ -13,8 +13,8 @@
"mdx",
"plugin",
"---Features---",
+ "include",
"last-modified",
- "image",
"manifest"
]
}
diff --git a/apps/docs/content/docs/mdx/performance.mdx b/apps/docs/content/docs/mdx/performance.mdx
index e589d693f..255392afb 100644
--- a/apps/docs/content/docs/mdx/performance.mdx
+++ b/apps/docs/content/docs/mdx/performance.mdx
@@ -4,16 +4,37 @@ description: The performance of Fumadocs MDX
icon: Rocket
---
-## About
+## Overview
Fumadocs MDX is a bundler plugin, in other words, it has a higher performance bottleneck.
With bundlers like Webpack and Turbopack, it is enough for large docs sites with nearly 500+ MDX files, which is sufficient for almost all docs sites.
-### Advantages
-
Since Fumadocs MDX works with your bundler, you can import any files including client components in your MDX files.
This allows a high flexibility and ensures everything is optimized by default.
+### Image Optimization
+
+Fumadocs MDX resolves images into static imports with [Remark Image](/docs/headless/mdx/remark-image).
+Therefore, your images will be optimized automatically by the Next.js Image API.
+
+```mdx
+![Hello](./hello.png)
+
+or in public folder
+
+![Hello](/hello.png)
+```
+
+Yields:
+
+```mdx
+import HelloImage from './hello.png';
+
+
+```
+
+![Banner](/banner.png)
+
### Caveats
Although Fumadocs MDX can handle nearly 500+ files, you are recommended to use other alternatives when the amount of MDX files exceeds 400.
@@ -26,22 +47,9 @@ This is because of:
## Alternative
-A popular alternative is `next-mdx-remote`, it allows rendering pages on-demand with SSG which can **highly increase your build speed.**
-However, you cannot use import in MDX files anymore.
-
### Remote Source
-`next-mdx-remote` supports remote sources.
-
-For a huge amount of files, we would also recommend using a remote source like GitHub.
-This allows you to fetch file content directly from your remote source and render it on-demand.
-
-This is very important because without a remote source to hold your files, the content of these files will be a part of your build.
-Since host platforms like Vercel are not supposed to hold a huge build output, you might hit the limit of their plans.
-
-### Integration
-
-Fumadocs doesn't provide an integration for `next-mdx-remote`, the main reason is `next-mdx-remote` supports remote sources, which are not necessarily file-system based.
-Fumadocs cannot generate a page tree for non file-system based sources.
+Remote sources don't need to pre-compile MDX files, it can compile them on-demand with SSG which can **highly increase your build speed.**
+However, you cannot use import in MDX files anymore.
-To use `next-mdx-remote` with Fumadocs, see [MDX Remote](/docs/headless/remote) for configuring remote sources.
+See [Remote Sources](/docs/headless/remote) for configuring remote sources.
diff --git a/apps/docs/content/docs/ui/index.mdx b/apps/docs/content/docs/ui/index.mdx
index e979c0915..cfa625b7b 100644
--- a/apps/docs/content/docs/ui/index.mdx
+++ b/apps/docs/content/docs/ui/index.mdx
@@ -24,7 +24,7 @@ Handles most of the logic, including document search, content source adapters, a
} title='Fumadocs UI'>
-The default theme of Fumadocs, it offers a beautiful look for documentation sites and many interactive components.
+The default theme of Fumadocs, it offers a beautiful look for documentation sites and interactive components.
@@ -55,8 +55,8 @@ You can take this site as an example of docs site built with Fumadocs.
Since Next.js is already a powerful framework, most features can be implemented with **only Next.js**.
-Fumadocs can process the content (e.g. syntax highlighting), and you can use the default theme (Fumadocs UI) or your custom theme.
-It is particularly helpful if you want to build complicated things like search.
+Fumadocs provides additional tooling for Next.js, including syntax highlighting, document search, and a default theme (Fumadocs UI).
+It helps you to avoid reinventing the wheels.
### Terminology
@@ -64,7 +64,7 @@ For a better understanding of the docs, there are some common terminologies you
**Page Tree:** A tree of all the pages, separators and folders. It is essential for navigation elements, usually generated by the content source or hardcoded. See [Types definitions](/docs/headless/page-tree).
-**Markdown (MDX) File:** A Markdown file could be located in a folder, or available remotely.
+**Markdown/MDX:** Markdown is a markup language for creating formatted text. By default, Fumadocs supports Markdown and MDX (superset of Markdown).
Some basic knowledge of Next.js App Router would be useful for further customisations.
@@ -96,6 +96,7 @@ It will ask you the following questions:
- Which content source to use? (Recommended: Fumadocs MDX)
- Configure Tailwind CSS?
+- Add ESLint config?
- Install dependencies?
After that, it will initialize a new fumadocs app.
@@ -103,7 +104,7 @@ Now you can start hacking!
### Enjoy!
-Create your first mdx file in the docs folder.
+Create your first MDX file in the docs folder.
```mdx title="content/docs/index.mdx"
---
@@ -123,7 +124,7 @@ npm run dev
In the project, you can see:
-- `lib/source.ts`: Where you organize code for content source adapter, we use [`loader`](/docs/headless/source-api) to read from content source.
+- `lib/source.ts`: Where you organize code for content source adapter, we use [`loader`](/docs/headless/source-api) API to load from your content source.
- `app/layout.config.tsx`: Shared options for layouts, this is optional but preferred to keep.
- `app/(home)`: The route group for your landing page and other pages.
- `app/docs`: The documentation layout and pages.
@@ -137,8 +138,7 @@ For Fumadocs MDX, see [Introduction](/docs/mdx) for details:
- `source.config.ts`: Config file for Fumadocs MDX.
-For other formats and content sources, they can be supported using a custom adapter.
-If you use a custom content source (e.g. Notion), you can [build a custom `loader` instead](/docs/headless/remote).
+For other formats and content sources that is not supported by default (e.g. Sanity), you can [build a custom content source](/docs/headless/remote).
## FAQ
diff --git a/apps/docs/content/docs/ui/internationalization.mdx b/apps/docs/content/docs/ui/internationalization.mdx
index 1e893357b..195e1e328 100644
--- a/apps/docs/content/docs/ui/internationalization.mdx
+++ b/apps/docs/content/docs/ui/internationalization.mdx
@@ -4,6 +4,13 @@ description: Support multiple languages in your documentation
icon: Globe
---
+
+
+ Fumadocs is not a full-powered i18n library, you can also use other libraries like [next-intl](https://github.com/amannn/next-intl).
+ I18n functionality of Fumadocs works without the built-in middleware.
+
+
+
You can setup i18n using Fumadocs CLI or update the configurations manually.
> Read the [Next.js Docs](https://nextjs.org/docs/app/building-your-application/routing/internationalization) to learn more about implementing I18n in Next.js.
@@ -42,7 +49,7 @@ export const source = loader({
});
```
-Update the usages to your source:
+Update the usages to your source to include a locale code:
```ts
import { source } from '@/lib/source';
@@ -57,7 +64,8 @@ source.getPage(params.slug, params.lang);
source.getPages(params.lang);
```
-See [Source API](/docs/headless/source-api) for other usages.
+Note that without providing a locale code, it uses your default locale instead.
+You can see [Source API](/docs/headless/source-api) for other usages.
### Middleware
@@ -105,20 +113,6 @@ export default function RootLayout({
}
```
-### Docs Page
-
-Make sure to update references to `source` and configure Static Site Generation correctly.
-
-```json doc-gen:file
-{
- "file": "../../examples/i18n/app/[lang]/docs/[[...slug]]/page.tsx",
- "codeblock": {
- "lang": "tsx",
- "meta": "title=\"page.tsx\""
- }
-}
-```
-
### Writing Documents
see [Page Conventions](/docs/ui/page-conventions#internationalization) to learn how to organize your documents.
@@ -171,3 +165,17 @@ export const baseOptions: BaseLayoutProps = {
i18n: true,
};
```
+
+## Navigation
+
+Fumadocs will only handle i18n navigation for its own layouts (e.g. sidebar components),
+you have to re-create components like `` from `next/link` and `useParams` hook to auto attend the locale to `href`.
+
+In addition, the [`fumadocs-core/dynamic-link`](/docs/headless/components/link#dynamic-hrefs) component supports linking to dynamic hrefs, including the locale prefix.
+For Markdown/MDX content, you may use it instead of the default anchor (`a`) component:
+
+```mdx title="content.mdx"
+import { DynamicLink } from 'fumadocs-core/dynamic-link';
+
+This is a link
+```
diff --git a/apps/docs/content/docs/ui/page-conventions.mdx b/apps/docs/content/docs/ui/page-conventions.mdx
index 1ecb2faaa..e0a281879 100644
--- a/apps/docs/content/docs/ui/page-conventions.mdx
+++ b/apps/docs/content/docs/ui/page-conventions.mdx
@@ -1,6 +1,6 @@
---
title: Organizing Pages
description: Organize documents with file-system based routing
-_mdx:
- mirror: ../headless/page-conventions.mdx
---
+
+../headless/page-conventions.mdx
diff --git a/apps/docs/scripts/lint.mts b/apps/docs/scripts/lint.mts
index 9bfc1c084..37a06a9c1 100644
--- a/apps/docs/scripts/lint.mts
+++ b/apps/docs/scripts/lint.mts
@@ -5,6 +5,7 @@ import { getTableOfContents } from 'fumadocs-core/server';
import fs from 'node:fs/promises';
import path from 'node:path';
import matter from 'gray-matter';
+import { remarkInclude } from 'fumadocs-mdx/config';
async function readFromPath(file: string) {
const content = await fs
@@ -28,23 +29,44 @@ async function checkLinks() {
await fg('content/blog/**/*.mdx').then((files) => files.map(readFromPath)),
);
+ const docs = docsFiles.map(async (file) => {
+ const info = parseFilePath(path.relative('content/docs', file.path));
+
+ return {
+ value: getSlugs(info),
+ hashes: (
+ await getTableOfContents(
+ {
+ path: file.path,
+ value: file.content,
+ },
+ [remarkInclude],
+ )
+ ).map((item) => item.url.slice(1)),
+ };
+ });
+
+ const blogs = blogFiles.map(async (file) => {
+ const info = parseFilePath(path.relative('content/blog', file.path));
+
+ return {
+ value: getSlugs(info)[0],
+ hashes: (
+ await getTableOfContents(
+ {
+ path: file.path,
+ value: file.content,
+ },
+ [remarkInclude],
+ )
+ ).map((item) => item.url.slice(1)),
+ };
+ });
+
const scanned = await scanURLs({
populate: {
- '(home)/blog/[slug]': blogFiles.map((file) => {
- const info = parseFilePath(path.relative('content/blog', file.path));
-
- return { value: getSlugs(info)[0] };
- }),
- 'docs/[...slug]': docsFiles.map((file) => {
- const info = parseFilePath(path.relative('content/docs', file.path));
-
- return {
- value: getSlugs(info),
- hashes: file.data?._mdx?.mirror
- ? undefined
- : getTableOfContents(file.content).map((item) => item.url.slice(1)),
- };
- }),
+ '(home)/blog/[slug]': await Promise.all(blogs),
+ 'docs/[...slug]': await Promise.all(docs),
},
});
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index 183a42f57..67b011767 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,5 +1,11 @@
# next-docs-zeta
+## 14.6.4
+
+### Patch Changes
+
+- b71064a: Support remark plugins & vfile input on `getTableOfContents`
+
## 14.6.3
## 14.6.2
diff --git a/packages/core/package.json b/packages/core/package.json
index ac22d52ee..a3ffdf7b7 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "fumadocs-core",
- "version": "14.6.3",
+ "version": "14.6.4",
"description": "The library for building a documentation website in Next.js",
"keywords": [
"NextJs",
@@ -116,7 +116,8 @@
"shiki-transformers": "^1.0.1",
"tsconfig": "workspace:*",
"typescript": "^5.7.2",
- "unified": "^11.0.5"
+ "unified": "^11.0.5",
+ "vfile": "^6.0.3"
},
"peerDependencies": {
"@oramacloud/client": "1.x.x || 2.x.x",
diff --git a/packages/core/src/server/get-toc.ts b/packages/core/src/server/get-toc.ts
index 2cca08e44..c93ed65b0 100644
--- a/packages/core/src/server/get-toc.ts
+++ b/packages/core/src/server/get-toc.ts
@@ -1,6 +1,8 @@
import { remark } from 'remark';
import type { ReactNode } from 'react';
import { remarkHeading } from '@/mdx-plugins/remark-heading';
+import type { PluggableList } from 'unified';
+import type { Compatible } from 'vfile';
export interface TOCItemType {
title: ReactNode;
@@ -13,9 +15,38 @@ export type TableOfContents = TOCItemType[];
/**
* Get Table of Contents from markdown/mdx document (using remark)
*
- * @param content - Markdown content
+ * @param content - Markdown content or file
*/
-export function getTableOfContents(content: string): TableOfContents {
+export function getTableOfContents(content: Compatible): TableOfContents;
+
+/**
+ * Get Table of Contents from markdown/mdx document (using remark)
+ *
+ * @param content - Markdown content or file
+ * @param remarkPlugins - remark plugins to be applied first
+ */
+export function getTableOfContents(
+ content: Compatible,
+ remarkPlugins: PluggableList,
+): Promise;
+
+export function getTableOfContents(
+ content: Compatible,
+ remarkPlugins?: PluggableList,
+): TableOfContents | Promise {
+ if (remarkPlugins) {
+ return remark()
+ .use(remarkPlugins)
+ .use(remarkHeading)
+ .process(content)
+ .then((result) => {
+ if ('toc' in result.data) return result.data.toc as TableOfContents;
+
+ return [];
+ });
+ }
+
+ // compatible with previous versions
const result = remark().use(remarkHeading).processSync(content);
if ('toc' in result.data) return result.data.toc as TableOfContents;
diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md
index 161da3541..f7126de85 100644
--- a/packages/create-app/CHANGELOG.md
+++ b/packages/create-app/CHANGELOG.md
@@ -1,5 +1,7 @@
# create-next-docs-app
+## 14.6.4
+
## 14.6.3
## 14.6.2
diff --git a/packages/create-app/package.json b/packages/create-app/package.json
index abee5fb9c..8e8ef1f5a 100644
--- a/packages/create-app/package.json
+++ b/packages/create-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-fumadocs-app",
- "version": "14.6.3",
+ "version": "14.6.4",
"description": "Create a new documentation site with Fumadocs",
"keywords": [
"NextJs",
diff --git a/packages/mdx/CHANGELOG.md b/packages/mdx/CHANGELOG.md
index 3dc04d0eb..64e716008 100644
--- a/packages/mdx/CHANGELOG.md
+++ b/packages/mdx/CHANGELOG.md
@@ -1,5 +1,13 @@
# next-docs-mdx
+## 11.2.1
+
+### Patch Changes
+
+- 3445182: Fix `include` hot-reload issues
+- Updated dependencies [b71064a]
+ - fumadocs-core@14.6.4
+
## 11.2.0
### Minor Changes
diff --git a/packages/mdx/package.json b/packages/mdx/package.json
index 137afdfa8..eaa6fc12c 100644
--- a/packages/mdx/package.json
+++ b/packages/mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "fumadocs-mdx",
- "version": "11.2.0",
+ "version": "11.2.1",
"description": "The built-in source for Fumadocs",
"keywords": [
"NextJs",
diff --git a/packages/mdx/src/config/cached.ts b/packages/mdx/src/config/cached.ts
index f4e3175ec..352bab796 100644
--- a/packages/mdx/src/config/cached.ts
+++ b/packages/mdx/src/config/cached.ts
@@ -1,5 +1,5 @@
import { createHash } from 'node:crypto';
-import fs from 'node:fs';
+import * as fs from 'node:fs';
import { loadConfig, type LoadedConfig } from '@/config/load';
const cache = new Map<
diff --git a/packages/mdx/src/config/index.ts b/packages/mdx/src/config/index.ts
index a232086fe..c1aafc6cd 100644
--- a/packages/mdx/src/config/index.ts
+++ b/packages/mdx/src/config/index.ts
@@ -2,3 +2,4 @@ export * from './types';
export * from './define';
export * from '../utils/mdx-options';
export { frontmatterSchema, metaSchema } from '../utils/schema';
+export * from '../mdx-plugins/remark-include';
diff --git a/packages/mdx/src/loader-mdx.ts b/packages/mdx/src/loader-mdx.ts
index e75456a7d..05662f378 100644
--- a/packages/mdx/src/loader-mdx.ts
+++ b/packages/mdx/src/loader-mdx.ts
@@ -19,15 +19,6 @@ export interface Options {
};
}
-interface InternalFrontmatter {
- _mdx?: {
- /**
- * Mirror another MDX file
- */
- mirror?: string;
- };
-}
-
export interface MetaFile {
path: string;
data: {
@@ -116,16 +107,6 @@ export default async function loader(
frontmatter = result.data as Record;
}
- const props = (matter.data as InternalFrontmatter)._mdx ?? {};
- if (props.mirror) {
- const mirrorPath = path.resolve(path.dirname(filePath), props.mirror);
- this.addDependency(mirrorPath);
-
- matter.content = await fs
- .readFile(mirrorPath)
- .then((res) => grayMatter(res.toString()).content);
- }
-
let timestamp: number | undefined;
if (config.global?.lastModifiedTime === 'git')
timestamp = (await getGitTimestamp(filePath))?.getTime();
diff --git a/packages/mdx/src/map/generate.ts b/packages/mdx/src/map/generate.ts
index 95dad8fd1..c32514c25 100644
--- a/packages/mdx/src/map/generate.ts
+++ b/packages/mdx/src/map/generate.ts
@@ -91,14 +91,14 @@ async function getCollectionFiles(
absolute: true,
});
- result.forEach((item) => {
- if (getTypeFromPath(item) !== collection.type) return;
+ for (const item of result) {
+ if (getTypeFromPath(item) !== collection.type) continue;
files.set(item, {
path: path.relative(dir, item),
absolutePath: item,
});
- });
+ }
}),
);
diff --git a/packages/mdx/src/map/index.ts b/packages/mdx/src/map/index.ts
index d90214b4c..b04fc78bb 100644
--- a/packages/mdx/src/map/index.ts
+++ b/packages/mdx/src/map/index.ts
@@ -1,5 +1,5 @@
-import path from 'node:path';
-import fs from 'node:fs';
+import * as path from 'node:path';
+import * as fs from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises';
import grayMatter from 'gray-matter';
import { getConfigHash, loadConfigCached } from '@/config/cached';
diff --git a/packages/mdx/src/map/manifest.ts b/packages/mdx/src/map/manifest.ts
index b3747bd53..7fcdb66a6 100644
--- a/packages/mdx/src/map/manifest.ts
+++ b/packages/mdx/src/map/manifest.ts
@@ -1,5 +1,5 @@
-import fs from 'node:fs';
-import path from 'node:path';
+import * as fs from 'node:fs';
+import * as path from 'node:path';
import { type LoadedConfig } from '@/config/load';
import type { MetaFile } from '@/loader-mdx';
import { getTypeFromPath } from '@/utils/get-type-from-path';
diff --git a/packages/mdx/src/mdx-plugins/remark-include.ts b/packages/mdx/src/mdx-plugins/remark-include.ts
new file mode 100644
index 000000000..c6993954c
--- /dev/null
+++ b/packages/mdx/src/mdx-plugins/remark-include.ts
@@ -0,0 +1,56 @@
+import type { Processor, Transformer } from 'unified';
+import { visit } from 'unist-util-visit';
+import type { Literal, Root } from 'mdast';
+import * as path from 'node:path';
+import * as fs from 'node:fs/promises';
+import matter from 'gray-matter';
+
+export function remarkInclude(this: Processor): Transformer {
+ return async (tree, file) => {
+ const queue: Promise[] = [];
+
+ visit(tree, ['mdxJsxFlowElement', 'paragraph'] as const, (node) => {
+ let specifier: string | undefined;
+
+ // without MDX, parse from paragraph nodes
+ if (node.type === 'paragraph' && node.children.length === 3) {
+ const [open, content, closure] = node.children;
+
+ if (
+ open.type === 'html' &&
+ open.value === '' &&
+ content.type === 'text' &&
+ closure.type === 'html' &&
+ closure.value === ''
+ ) {
+ specifier = content.value.trim();
+ }
+ }
+
+ if (node.type === 'mdxJsxFlowElement' && node.name === 'include') {
+ const child = node.children.at(0) as Literal | undefined;
+
+ if (!child || child.type !== 'text') return;
+ specifier = child.value;
+ }
+
+ if (!specifier) return 'skip';
+ const targetPath = path.resolve(path.dirname(file.path), specifier);
+
+ queue.push(
+ fs.readFile(targetPath).then((content) => {
+ const parsed = this.parse(matter(content).content);
+
+ if (file.data._compiler) {
+ file.data._compiler.addDependency(targetPath);
+ }
+ Object.assign(node, parsed);
+ }),
+ );
+
+ return 'skip';
+ });
+
+ await Promise.all(queue);
+ };
+}
diff --git a/packages/mdx/src/postinstall.ts b/packages/mdx/src/postinstall.ts
index 78ff4c216..ffc4e8940 100644
--- a/packages/mdx/src/postinstall.ts
+++ b/packages/mdx/src/postinstall.ts
@@ -1,5 +1,5 @@
-import path from 'node:path';
-import fs from 'node:fs';
+import * as path from 'node:path';
+import * as fs from 'node:fs';
import { findConfigFile, loadConfig } from '@/config/load';
import { generateTypes } from '@/map/generate';
diff --git a/packages/mdx/src/utils/build-mdx.ts b/packages/mdx/src/utils/build-mdx.ts
index 64db440c6..e7368aa25 100644
--- a/packages/mdx/src/utils/build-mdx.ts
+++ b/packages/mdx/src/utils/build-mdx.ts
@@ -1,12 +1,6 @@
import { createProcessor, type ProcessorOptions } from '@mdx-js/mdx';
import type { VFile } from 'vfile';
-import type { Transformer } from 'unified';
-import { visit } from 'unist-util-visit';
-import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
-import type { Literal } from 'mdast';
-import { readFileSync } from 'node:fs';
-import * as path from 'node:path';
-import matter from 'gray-matter';
+import { remarkInclude } from '@/mdx-plugins/remark-include';
type Processor = ReturnType;
@@ -41,27 +35,13 @@ function cacheKey(group: string, format: string): string {
return `${group}:${format}`;
}
-function remarkInclude(this: Processor, options: CompilerOptions): Transformer {
- return (tree, file) => {
- visit(tree, 'mdxJsxFlowElement', (node: MdxJsxFlowElement) => {
- if (node.name === 'include') {
- const child = node.children.at(0) as Literal | undefined;
-
- if (!child || child.type !== 'text') return;
- const specifier = child.value;
-
- const targetPath = path.resolve(path.dirname(file.path), specifier);
-
- const content = readFileSync(targetPath);
- const parsed = this.parse(matter(content).content);
-
- options.addDependency(targetPath);
- Object.assign(node, parsed);
- }
-
- return 'skip';
- });
- };
+declare module 'vfile' {
+ interface DataMap {
+ /**
+ * The compiler object from loader
+ */
+ _compiler?: CompilerOptions;
+ }
}
/**
@@ -93,12 +73,7 @@ export function buildMDX(
outputFormat: 'program',
development: process.env.NODE_ENV === 'development',
...rest,
- remarkPlugins: [
- options._compiler
- ? ([remarkInclude, options._compiler] as any)
- : null,
- ...(rest.remarkPlugins ?? []),
- ],
+ remarkPlugins: [remarkInclude, ...(rest.remarkPlugins ?? [])],
format,
}),
@@ -114,6 +89,7 @@ export function buildMDX(
data: {
...data,
frontmatter,
+ _compiler: options._compiler,
},
});
}
diff --git a/packages/openapi/CHANGELOG.md b/packages/openapi/CHANGELOG.md
index c894a7575..f5698ed10 100644
--- a/packages/openapi/CHANGELOG.md
+++ b/packages/openapi/CHANGELOG.md
@@ -1,5 +1,15 @@
# @fuma-docs/openapi
+## 5.10.2
+
+### Patch Changes
+
+- Updated dependencies [b71064a]
+- Updated dependencies [67124b1]
+- Updated dependencies [1810868]
+ - fumadocs-core@14.6.4
+ - fumadocs-ui@14.6.4
+
## 5.10.1
### Patch Changes
diff --git a/packages/openapi/package.json b/packages/openapi/package.json
index 2d81c0b61..c25350037 100644
--- a/packages/openapi/package.json
+++ b/packages/openapi/package.json
@@ -1,6 +1,6 @@
{
"name": "fumadocs-openapi",
- "version": "5.10.1",
+ "version": "5.10.2",
"description": "Generate MDX docs for your OpenAPI spec",
"keywords": [
"NextJs",
diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md
index 43f3869b4..c2723ecc4 100644
--- a/packages/ui/CHANGELOG.md
+++ b/packages/ui/CHANGELOG.md
@@ -1,5 +1,14 @@
# next-docs-ui
+## 14.6.4
+
+### Patch Changes
+
+- 67124b1: Improve theme toggle on Notebook layout
+- 1810868: Enable `content-visibility` CSS features
+- Updated dependencies [b71064a]
+ - fumadocs-core@14.6.4
+
## 14.6.3
### Patch Changes
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 99311bed9..7a5cf8eeb 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "fumadocs-ui",
- "version": "14.6.3",
+ "version": "14.6.4",
"description": "The framework for building a documentation website in Next.js",
"keywords": [
"NextJs",
diff --git a/packages/ui/src/layouts/docs.tsx b/packages/ui/src/layouts/docs.tsx
index 135b7168f..81f5fc0ff 100644
--- a/packages/ui/src/layouts/docs.tsx
+++ b/packages/ui/src/layouts/docs.tsx
@@ -19,7 +19,6 @@ import {
type IconItemType,
BaseLinkItem,
} from '@/layouts/links';
-import { getSidebarTabs, type TabOptions } from '@/utils/get-sidebar-tabs';
import { RootToggle } from '@/components/layout/root-toggle';
import { type BaseLayoutProps, getLinks } from './shared';
import {
@@ -268,4 +267,5 @@ function SidebarFooterItems({
);
}
-export { getSidebarTabs, type TabOptions, type LinkItemType };
+export { getSidebarTabsFromOptions, type TabOptions } from './docs/shared';
+export { type LinkItemType };
diff --git a/packages/ui/src/layouts/docs/shared.tsx b/packages/ui/src/layouts/docs/shared.tsx
index d7e94093a..6bacbdba1 100644
--- a/packages/ui/src/layouts/docs/shared.tsx
+++ b/packages/ui/src/layouts/docs/shared.tsx
@@ -10,7 +10,6 @@ import {
import { cn } from '@/utils/cn';
import { buttonVariants } from '@/components/ui/button';
import type { PageTree } from 'fumadocs-core/server';
-import { getSidebarTabs, type TabOptions } from '@/utils/get-sidebar-tabs';
import type { FC, ReactNode } from 'react';
import type { Option } from '@/components/layout/root-toggle';
@@ -18,6 +17,10 @@ export const layoutVariables = {
'--fd-layout-offset': 'max(calc(50vw - var(--fd-layout-width) / 2), 0px)',
};
+export interface TabOptions {
+ transform?: (option: Option, node: PageTree.Folder) => Option | null;
+}
+
export interface SidebarOptions extends SidebarProps {
enabled: boolean;
component: ReactNode;
@@ -108,3 +111,63 @@ export function getSidebarTabsFromOptions(
return getSidebarTabs(tree);
}
}
+
+const defaultTransform: TabOptions['transform'] = (option, node) => {
+ if (!node.icon) return option;
+
+ return {
+ ...option,
+ icon: (
+