├── .changeset ├── common-owls-relax.md ├── config.json └── curly-frogs-cheat.md ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature-request.yml ├── contributing.md └── workflows │ ├── lint.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .idea ├── .gitignore ├── fumadocs.iml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── prettier.xml └── vcs.xml ├── .prettierignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── apps └── docs │ ├── .env │ ├── .gitignore │ ├── app │ ├── (home) │ │ ├── arch.png │ │ ├── blog │ │ │ ├── [slug] │ │ │ │ ├── page.client.tsx │ │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ ├── rss.xml │ │ │ │ └── route.ts │ │ │ └── test │ │ │ │ └── page.mdx │ │ ├── docs │ │ │ └── page.tsx │ │ ├── icons.tsx │ │ ├── layout.tsx │ │ ├── main.png │ │ ├── marquee.tsx │ │ ├── openapi.png │ │ ├── page.client.tsx │ │ ├── page.tsx │ │ ├── showcase │ │ │ ├── design.png │ │ │ └── page.tsx │ │ ├── sponsors │ │ │ ├── data.tsx │ │ │ └── page.tsx │ │ └── uwu.tsx │ ├── api │ │ ├── chat │ │ │ └── route.ts │ │ └── proxy │ │ │ └── route.ts │ ├── docs │ │ ├── [...slug] │ │ │ ├── page.client.tsx │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── global.css │ ├── icon.png │ ├── layout.client.tsx │ ├── layout.config.tsx │ ├── layout.tsx │ ├── llms-full.txt │ │ └── route.ts │ ├── llms.mdx │ │ └── [...slug] │ │ │ └── route.ts │ ├── llms.txt │ │ └── route.ts │ ├── og │ │ └── [...slug] │ │ │ ├── JetBrainsMono-Bold.ttf │ │ │ ├── JetBrainsMono-Regular.ttf │ │ │ ├── og.tsx │ │ │ └── route.tsx │ ├── provider.tsx │ ├── sitemap.ts │ └── static.json │ │ └── route.ts │ ├── components │ ├── ai │ │ ├── index.tsx │ │ ├── markdown-processor.ts │ │ └── search.tsx │ ├── code-block.tsx │ ├── contributor-count.tsx │ ├── mdx │ │ └── mermaid.tsx │ ├── preview │ │ ├── dynamic-codeblock.tsx │ │ ├── index.tsx │ │ ├── lazy.ts │ │ └── wrapper.tsx │ ├── rate.tsx │ ├── registry.mts │ ├── search.tsx │ ├── ui-overview.tsx │ └── ui │ │ ├── button.tsx │ │ └── hover-card.tsx │ ├── content │ ├── blog │ │ ├── 2024-5-15.mdx │ │ ├── 2024-5-16.mdx │ │ ├── customise-ui.mdx │ │ ├── make-a-blog.mdx │ │ ├── mdx-v10-summary.mdx │ │ ├── mdx-v10.mdx │ │ ├── new-conventions.mdx │ │ ├── openapi-v9.mdx │ │ ├── v12-after.mdx │ │ ├── v12.mdx │ │ ├── v13.mdx │ │ ├── v14.mdx │ │ ├── v15-2.mdx │ │ ├── v15.mdx │ │ └── why-docs.mdx │ ├── docs │ │ ├── cli │ │ │ ├── index.mdx │ │ │ └── meta.json │ │ ├── headless │ │ │ ├── components │ │ │ │ ├── breadcrumb.mdx │ │ │ │ ├── index.mdx │ │ │ │ ├── link.mdx │ │ │ │ ├── meta.json │ │ │ │ ├── sidebar.mdx │ │ │ │ ├── toc-example.tsx │ │ │ │ └── toc.mdx │ │ │ ├── content-collections │ │ │ │ └── index.mdx │ │ │ ├── custom-source.mdx │ │ │ ├── index.mdx │ │ │ ├── internationalization.mdx │ │ │ ├── mdx │ │ │ │ ├── headings.mdx │ │ │ │ ├── index.mdx │ │ │ │ ├── install.mdx │ │ │ │ ├── meta.json │ │ │ │ ├── rehype-code.mdx │ │ │ │ ├── remark-admonition.mdx │ │ │ │ ├── remark-image.mdx │ │ │ │ ├── remark-ts2js.mdx │ │ │ │ └── structure.mdx │ │ │ ├── meta.json │ │ │ ├── page-conventions.mdx │ │ │ ├── page-tree.mdx │ │ │ ├── props.ts │ │ │ ├── search │ │ │ │ ├── algolia.mdx │ │ │ │ ├── index.mdx │ │ │ │ ├── orama-cloud.mdx │ │ │ │ ├── orama.mdx │ │ │ │ ├── sync-algolia.mjs │ │ │ │ └── trieve.mdx │ │ │ ├── source-api.mdx │ │ │ └── utils │ │ │ │ ├── find-neighbour.mdx │ │ │ │ ├── get-toc.mdx │ │ │ │ ├── git-last-edit.mdx │ │ │ │ ├── index.mdx │ │ │ │ └── meta.json │ │ ├── mdx │ │ │ ├── async.mdx │ │ │ ├── collections.mdx │ │ │ ├── global.mdx │ │ │ ├── include.mdx │ │ │ ├── index.mdx │ │ │ ├── last-modified.mdx │ │ │ ├── mdx.mdx │ │ │ ├── meta.json │ │ │ ├── page.mdx │ │ │ ├── performance.mdx │ │ │ ├── plugin.mdx │ │ │ └── props.ts │ │ ├── meta.json │ │ ├── openapi │ │ │ ├── index.mdx │ │ │ └── meta.json │ │ └── ui │ │ │ ├── (integrations) │ │ │ ├── feedback.mdx │ │ │ ├── get-llm-text.ts │ │ │ ├── llms.mdx │ │ │ ├── llms.mdx.ts │ │ │ ├── llms.txt.ts │ │ │ ├── open-graph.mdx │ │ │ ├── openapi │ │ │ │ ├── configurations.mdx │ │ │ │ ├── index.mdx │ │ │ │ ├── media-adapters.mdx │ │ │ │ ├── meta.json │ │ │ │ └── proxy.mdx │ │ │ ├── python.mdx │ │ │ ├── rss.mdx │ │ │ └── typescript.mdx │ │ │ ├── comparisons.mdx │ │ │ ├── components │ │ │ ├── accordion.mdx │ │ │ ├── auto-type-table.mdx │ │ │ ├── banner.mdx │ │ │ ├── dynamic-codeblock.mdx │ │ │ ├── files.mdx │ │ │ ├── github-info.mdx │ │ │ ├── image-zoom.mdx │ │ │ ├── index.mdx │ │ │ ├── inline-toc.mdx │ │ │ ├── server-codeblock.tsx │ │ │ ├── steps.mdx │ │ │ ├── tabs.client.tsx │ │ │ ├── tabs.mdx │ │ │ └── type-table.mdx │ │ │ ├── customisation.mdx │ │ │ ├── index.mdx │ │ │ ├── internationalization.mdx │ │ │ ├── layouts │ │ │ ├── docs.mdx │ │ │ ├── home-layout.mdx │ │ │ ├── notebook.mdx │ │ │ ├── page.mdx │ │ │ └── root-provider.mdx │ │ │ ├── manual-installation.mdx │ │ │ ├── markdown │ │ │ ├── index.mdx │ │ │ ├── math.mdx │ │ │ ├── mermaid.mdx │ │ │ └── twoslash.mdx │ │ │ ├── mdx │ │ │ ├── codeblock.mdx │ │ │ └── index.mdx │ │ │ ├── meta.json │ │ │ ├── navigation │ │ │ ├── index.mdx │ │ │ ├── links.mdx │ │ │ └── sidebar.mdx │ │ │ ├── page-conventions.mdx │ │ │ ├── props.ts │ │ │ ├── search-algolia.tsx │ │ │ ├── search.mdx │ │ │ ├── static-export.mdx │ │ │ ├── theme.client.tsx │ │ │ ├── theme.mdx │ │ │ ├── versioning.mdx │ │ │ └── what-is-fumadocs.mdx │ └── shared │ │ └── page-conventions.i18n.mdx │ ├── eslint.config.mjs │ ├── lib │ ├── chat │ │ └── inkeep-qa-schema.ts │ ├── cn.ts │ ├── get-contributors.ts │ ├── get-llm-text.ts │ ├── get-sponsors.ts │ ├── github.ts │ ├── metadata.ts │ └── source.ts │ ├── mdx-components.tsx │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── banner.png │ ├── blog │ │ ├── img.png │ │ ├── links-menu.png │ │ ├── toc-popover.png │ │ ├── v12-root-toggle.png │ │ ├── v12.png │ │ └── v14-navbar.png │ ├── circuit_2.svg │ ├── content-collections.webp │ ├── docs │ │ ├── docs-nav.png │ │ ├── nav-layout-docs.png │ │ ├── nav-layout-home.png │ │ ├── nav-layout-menu.png │ │ ├── notebook-nav-mode.png │ │ ├── notebook-tab-mode.png │ │ ├── notebook.png │ │ ├── sidebar-tabs.png │ │ ├── sidebar.png │ │ └── webhook.png │ ├── logo.png │ ├── robots.txt │ ├── showcases │ │ ├── arktype.png │ │ ├── assistant-ui.png │ │ ├── better-auth.png │ │ ├── codehike.png │ │ ├── dokploy.png │ │ ├── expostarter.png │ │ ├── hexta-ui.png │ │ ├── hiro.png │ │ ├── million.png │ │ ├── mix-space.png │ │ ├── next-faq.png │ │ ├── nuqs.jpg │ │ ├── openpanel.png │ │ ├── sunar.png │ │ ├── supastarter.png │ │ ├── turbostarter.png │ │ ├── vision-ui.png │ │ └── zen-browser.png │ ├── source.png │ ├── spot.png │ └── themes │ │ ├── black.png │ │ ├── catppuccin.png │ │ ├── dusk.png │ │ ├── neutral.png │ │ ├── ocean.png │ │ ├── purple.png │ │ └── vitepress.png │ ├── scalar.yaml │ ├── scripts │ ├── build-registry.mts │ ├── lint.mts │ ├── post-build.mts │ ├── pre-build.mts │ └── update-orama-index.mts │ ├── source.config.ts │ └── tsconfig.json ├── documents └── logo.png ├── examples ├── README.md ├── content-collections │ ├── .gitignore │ ├── app │ │ ├── (home) │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── api │ │ │ └── search │ │ │ │ └── route.ts │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── layout.config.tsx │ │ └── layout.tsx │ ├── content-collections.ts │ ├── content │ │ └── docs │ │ │ ├── index.mdx │ │ │ └── test.mdx │ ├── lib │ │ └── source.ts │ ├── mdx-components.tsx │ ├── next.config.mjs │ ├── package.json │ └── tsconfig.json ├── i18n │ ├── app │ │ ├── [lang] │ │ │ ├── (home) │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ │ ├── docs-og │ │ │ │ └── [...slug] │ │ │ │ │ └── route.ts │ │ │ ├── docs │ │ │ │ ├── [[...slug]] │ │ │ │ │ └── page.tsx │ │ │ │ └── layout.tsx │ │ │ └── layout.tsx │ │ ├── api │ │ │ └── search │ │ │ │ ├── route-full.ts │ │ │ │ └── route.ts │ │ └── layout.config.tsx │ ├── content │ │ └── docs │ │ │ ├── index.cn.mdx │ │ │ ├── index.mdx │ │ │ └── test.mdx │ ├── lib │ │ ├── i18n.ts │ │ ├── metadata.ts │ │ └── source.ts │ ├── mdx-components.tsx │ ├── middleware.ts │ ├── next.config.mjs │ ├── package.json │ ├── source.config.ts │ └── tsconfig.json ├── next-mdx │ ├── app │ │ ├── (home) │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── api │ │ │ └── search │ │ │ │ ├── route-full.ts │ │ │ │ ├── route-tag.ts │ │ │ │ └── route.ts │ │ ├── docs-og │ │ │ └── [...slug] │ │ │ │ └── route.tsx │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── global.css │ │ ├── layout.config.tsx │ │ ├── layout.tsx │ │ └── static.json │ │ │ └── orama-cloud.ts │ ├── content │ │ └── docs │ │ │ ├── index.mdx │ │ │ ├── test.mdx │ │ │ └── test2.mdx │ ├── lib │ │ └── source.ts │ ├── mdx-components.tsx │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── scripts │ │ └── sync-orama-cloud.mjs │ ├── source.config.ts │ └── tsconfig.json ├── openapi │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── (home) │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── api │ │ │ └── search │ │ │ │ └── route.ts │ │ ├── docs-og │ │ │ └── [...slug] │ │ │ │ └── route.ts │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── global.css │ │ ├── layout.config.tsx │ │ ├── layout.tsx │ │ └── logo.tsx │ ├── content │ │ └── docs │ │ │ ├── auth.mdx │ │ │ ├── index.mdx │ │ │ └── meta.json │ ├── lib │ │ └── source.ts │ ├── mdx-components.tsx │ ├── next.config.mjs │ ├── openapi.json │ ├── package.json │ ├── postcss.config.mjs │ ├── scripts │ │ └── generate-docs.mjs │ ├── source.config.ts │ └── tsconfig.json ├── python │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── (home) │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── api │ │ │ └── search │ │ │ │ └── route.ts │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── global.css │ │ ├── layout.config.tsx │ │ └── layout.tsx │ ├── content │ │ └── docs │ │ │ └── index.mdx │ ├── lib │ │ └── source.ts │ ├── mdx-components.tsx │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── scripts │ │ └── generate-docs.mjs │ ├── source.config.ts │ └── tsconfig.json ├── react-router │ ├── .gitignore │ ├── app │ │ ├── app.css │ │ ├── docs │ │ │ ├── page.tsx │ │ │ └── search.ts │ │ ├── root.tsx │ │ ├── routes.ts │ │ ├── routes │ │ │ └── home.tsx │ │ └── source.ts │ ├── content │ │ └── docs │ │ │ ├── index.mdx │ │ │ ├── test.mdx │ │ │ └── test2.mdx │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── react-router.config.ts │ ├── tsconfig.json │ └── vite.config.ts ├── remote-mdx │ ├── app │ │ ├── api │ │ │ └── search │ │ │ │ └── route.ts │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── utils.ts │ │ ├── layout.tsx │ │ └── page.tsx │ ├── content │ │ └── docs │ │ │ ├── comparisons.mdx │ │ │ ├── index.mdx │ │ │ ├── page-tree.ts │ │ │ └── test.mdx │ ├── next.config.ts │ ├── package.json │ └── tsconfig.json └── tanstack-start │ ├── .gitignore │ ├── app.config.ts │ ├── app │ ├── api.ts │ ├── app.css │ ├── client.tsx │ ├── router.tsx │ ├── routes │ │ ├── __root.tsx │ │ ├── api │ │ │ └── search.ts │ │ ├── docs │ │ │ └── $.tsx │ │ └── index.tsx │ └── ssr.tsx │ ├── content │ └── docs │ │ ├── index.mdx │ │ ├── test.mdx │ │ └── test2.mdx │ ├── lib │ └── source.ts │ ├── package.json │ └── tsconfig.json ├── package.json ├── packages ├── cli │ ├── .gitignore │ ├── CHANGELOG.md │ ├── eslint.config.mjs │ ├── package.json │ ├── scripts │ │ └── sync.ts │ ├── src │ │ ├── build │ │ │ ├── build-file.ts │ │ │ ├── build-registry.ts │ │ │ ├── component-builder.ts │ │ │ └── index.ts │ │ ├── commands │ │ │ ├── add.ts │ │ │ ├── customise.ts │ │ │ ├── file-tree.ts │ │ │ └── init.ts │ │ ├── config.ts │ │ ├── constants.ts │ │ ├── generated.d.ts │ │ ├── index.ts │ │ ├── plugins │ │ │ ├── i18n.ts │ │ │ ├── index.ts │ │ │ └── openapi.ts │ │ └── utils │ │ │ ├── add │ │ │ ├── get-deps.ts │ │ │ ├── install-component.ts │ │ │ └── install-deps.ts │ │ │ ├── file-tree │ │ │ └── run-tree.ts │ │ │ ├── fs.ts │ │ │ ├── get-package-manager.ts │ │ │ ├── i18n │ │ │ ├── transform-layout-config.ts │ │ │ ├── transform-root-layout.ts │ │ │ └── transform-source-i18n.ts │ │ │ ├── is-src.ts │ │ │ ├── move-files.ts │ │ │ ├── transform-references.ts │ │ │ ├── transform-tailwind.ts │ │ │ └── typescript.ts │ ├── test │ │ ├── fixture │ │ │ ├── layout │ │ │ └── layout.out │ │ ├── index.test.ts │ │ ├── out │ │ │ ├── extended │ │ │ │ ├── _registry.json │ │ │ │ ├── button.json │ │ │ │ ├── popover.json │ │ │ │ └── select.json │ │ │ └── repo │ │ │ │ ├── _registry.json │ │ │ │ ├── button.json │ │ │ │ └── popover.json │ │ ├── repo-2 │ │ │ ├── components │ │ │ │ └── select.ts │ │ │ ├── package.json │ │ │ ├── registry.ts │ │ │ └── tsconfig.json │ │ └── repo │ │ │ ├── components │ │ │ ├── button.tsx │ │ │ ├── nested │ │ │ │ └── hello.tsx │ │ │ └── popover.tsx │ │ │ ├── hooks │ │ │ └── use-example.ts │ │ │ ├── package.json │ │ │ ├── registry.ts │ │ │ ├── tsconfig.json │ │ │ └── utils │ │ │ └── example-util.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── content-collections │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── configuration.ts │ │ ├── index.ts │ │ ├── resolve-plugins.ts │ │ └── types.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── core │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── breadcrumb.tsx │ │ ├── content │ │ │ └── index.ts │ │ ├── dynamic-link.tsx │ │ ├── framework │ │ │ ├── index.tsx │ │ │ ├── next.tsx │ │ │ ├── react-router.tsx │ │ │ └── tanstack.tsx │ │ ├── hide-if-empty.tsx │ │ ├── highlight │ │ │ ├── client.tsx │ │ │ ├── index.ts │ │ │ └── shiki.ts │ │ ├── i18n │ │ │ ├── index.ts │ │ │ └── middleware.ts │ │ ├── link.tsx │ │ ├── mdx-plugins │ │ │ ├── hast-utils.ts │ │ │ ├── index.ts │ │ │ ├── rehype-code.ts │ │ │ ├── rehype-toc.ts │ │ │ ├── remark-admonition.ts │ │ │ ├── remark-code-tab.ts │ │ │ ├── remark-heading.ts │ │ │ ├── remark-image.ts │ │ │ ├── remark-steps.ts │ │ │ ├── remark-structure.ts │ │ │ ├── remark-utils.ts │ │ │ └── transformer-icon.ts │ │ ├── search │ │ │ ├── algolia.ts │ │ │ ├── client.ts │ │ │ ├── client │ │ │ │ ├── algolia.ts │ │ │ │ ├── fetch.ts │ │ │ │ ├── orama-cloud.ts │ │ │ │ └── static.ts │ │ │ ├── orama-cloud.ts │ │ │ ├── orama │ │ │ │ ├── _stemmers.ts │ │ │ │ ├── create-db.ts │ │ │ │ ├── create-endpoint.ts │ │ │ │ ├── create-from-source.ts │ │ │ │ ├── create-i18n.ts │ │ │ │ └── search │ │ │ │ │ ├── advanced.ts │ │ │ │ │ └── simple.ts │ │ │ └── server.ts │ │ ├── server │ │ │ ├── get-toc.ts │ │ │ ├── git-api.ts │ │ │ ├── index.ts │ │ │ ├── metadata.ts │ │ │ ├── page-tree.ts │ │ │ └── types.ts │ │ ├── sidebar.tsx │ │ ├── source │ │ │ ├── file-system.ts │ │ │ ├── index.ts │ │ │ ├── load-files.ts │ │ │ ├── loader.ts │ │ │ ├── page-tree-builder.ts │ │ │ ├── path.ts │ │ │ └── types.ts │ │ ├── toc.tsx │ │ └── utils │ │ │ ├── merge-refs.ts │ │ │ ├── page-tree.tsx │ │ │ ├── path.ts │ │ │ ├── remove-undefined.ts │ │ │ ├── use-anchor-observer.ts │ │ │ ├── use-debounce.ts │ │ │ ├── use-effect-event.ts │ │ │ ├── use-media-query.ts │ │ │ └── use-on-change.ts │ ├── test │ │ ├── fixtures │ │ │ ├── rehype-toc.md │ │ │ ├── rehype-toc.output.js │ │ │ ├── remark-admonition.md │ │ │ ├── remark-admonition.output.mdx │ │ │ ├── remark-heading.md │ │ │ ├── remark-heading.output.json │ │ │ ├── remark-image-public-dir.md │ │ │ ├── remark-image-public-dir.output.mdx │ │ │ ├── remark-image-without-import.output.mdx │ │ │ ├── remark-image.md │ │ │ ├── remark-image.output.json │ │ │ ├── remark-steps.md │ │ │ ├── remark-steps.output.md │ │ │ ├── remark-structure.md │ │ │ ├── remark-structure.output.json │ │ │ └── test.png │ │ ├── index.test.ts │ │ ├── loader.test.ts │ │ ├── mdx-plugins.test.ts │ │ ├── search.test.ts │ │ └── storage.test.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── create-app-versions │ └── package.json ├── create-app │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.mjs │ ├── package.json │ ├── scripts │ │ ├── update-git-repo.js │ │ └── update-git-repo.sh │ ├── src │ │ ├── auto-install.ts │ │ ├── constants.ts │ │ ├── create-app.ts │ │ ├── git.ts │ │ ├── index.ts │ │ └── versions.d.ts │ ├── template │ │ ├── +next+content-collections │ │ │ ├── app │ │ │ │ └── docs │ │ │ │ │ └── [[...slug]] │ │ │ │ │ └── page.tsx │ │ │ ├── content-collections.ts │ │ │ ├── lib │ │ │ │ └── source.ts │ │ │ ├── next.config.mjs │ │ │ └── tsconfig.json │ │ ├── +next+eslint │ │ │ └── .eslintrc.json │ │ ├── +next+fuma-docs-mdx │ │ │ ├── README.md │ │ │ ├── app │ │ │ │ └── docs │ │ │ │ │ └── [[...slug]] │ │ │ │ │ └── page.tsx │ │ │ ├── lib │ │ │ │ └── source.ts │ │ │ ├── next.config.mjs │ │ │ ├── source.config.ts │ │ │ └── tsconfig.json │ │ ├── +next+tailwindcss │ │ │ ├── app │ │ │ │ ├── (home) │ │ │ │ │ └── page.tsx │ │ │ │ ├── global.css │ │ │ │ └── layout.tsx │ │ │ └── postcss.config.mjs │ │ ├── +next │ │ │ ├── README.md │ │ │ ├── app │ │ │ │ ├── (home) │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── api │ │ │ │ │ └── search │ │ │ │ │ │ └── route.ts │ │ │ │ ├── docs │ │ │ │ │ └── layout.tsx │ │ │ │ ├── layout.config.tsx │ │ │ │ └── layout.tsx │ │ │ ├── content │ │ │ │ └── docs │ │ │ │ │ ├── index.mdx │ │ │ │ │ └── test.mdx │ │ │ ├── example.gitignore │ │ │ └── mdx-components.tsx │ │ ├── react-router │ │ │ ├── README.md │ │ │ ├── app │ │ │ │ ├── app.css │ │ │ │ ├── docs │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── search.ts │ │ │ │ ├── root.tsx │ │ │ │ ├── routes.ts │ │ │ │ ├── routes │ │ │ │ │ └── home.tsx │ │ │ │ └── source.ts │ │ │ ├── content │ │ │ │ └── docs │ │ │ │ │ ├── index.mdx │ │ │ │ │ └── test.mdx │ │ │ ├── example.gitignore │ │ │ ├── public │ │ │ │ └── favicon.ico │ │ │ ├── react-router.config.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ └── tanstack-start │ │ │ ├── README.md │ │ │ ├── app.config.ts │ │ │ ├── app │ │ │ ├── api.ts │ │ │ ├── app.css │ │ │ ├── client.tsx │ │ │ ├── router.tsx │ │ │ ├── routes │ │ │ │ ├── __root.tsx │ │ │ │ ├── api │ │ │ │ │ └── search.ts │ │ │ │ ├── docs │ │ │ │ │ └── $.tsx │ │ │ │ └── index.tsx │ │ │ └── ssr.tsx │ │ │ ├── content │ │ │ └── docs │ │ │ │ └── index.mdx │ │ │ ├── example.gitignore │ │ │ ├── lib │ │ │ └── source.ts │ │ │ └── tsconfig.json │ ├── tsconfig.json │ └── tsup.config.ts ├── doc-gen │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── file-generator.ts │ │ ├── index.ts │ │ ├── remark-docgen.ts │ │ ├── remark-install.ts │ │ ├── remark-show.ts │ │ ├── remark-ts2js.ts │ │ └── utils.ts │ ├── test │ │ ├── fixtures │ │ │ ├── file-gen.md │ │ │ ├── file-gen.output.md │ │ │ ├── file-gen.relative.md │ │ │ ├── file-gen.relative.output.md │ │ │ ├── remark-install-persist.md │ │ │ ├── remark-install-persist.output.jsx │ │ │ ├── remark-install.md │ │ │ ├── remark-install.output.jsx │ │ │ ├── remark-show.mdx │ │ │ ├── remark-show.output.jsx │ │ │ ├── sample.ts │ │ │ ├── sample.txt │ │ │ ├── ts2js.md │ │ │ └── ts2js.output.jsx │ │ └── index.test.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── eslint-config-custom │ ├── library.js │ ├── next.js │ └── package.json ├── mdx-remote │ ├── CHANGELOG.md │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── client │ │ │ └── index.ts │ │ ├── compile.ts │ │ ├── index.ts │ │ ├── render.ts │ │ └── utils.ts │ ├── test │ │ ├── fixtures │ │ │ ├── file.mdx │ │ │ ├── file.mdx.js │ │ │ ├── file.mdx.json │ │ │ ├── file.mdx.production.js │ │ │ ├── index.mdx │ │ │ ├── index.mdx.js │ │ │ ├── index.mdx.json │ │ │ └── index.mdx.production.js │ │ └── index.test.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── mdx │ ├── CHANGELOG.md │ ├── bin.js │ ├── eslint.config.mjs │ ├── loader-mdx.cjs │ ├── package.json │ ├── src │ │ ├── config │ │ │ ├── build.ts │ │ │ ├── define.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── loader-mdx.ts │ │ ├── map │ │ │ ├── file-cache.ts │ │ │ ├── generate.ts │ │ │ ├── index.ts │ │ │ └── watcher.ts │ │ ├── mdx-plugins │ │ │ ├── remark-exports.ts │ │ │ └── remark-include.ts │ │ ├── next │ │ │ ├── create.ts │ │ │ └── index.ts │ │ ├── postinstall.ts │ │ ├── runtime │ │ │ ├── async.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── utils │ │ │ ├── build-mdx.ts │ │ │ ├── config.ts │ │ │ ├── get-type-from-path.ts │ │ │ ├── git-timestamp.ts │ │ │ ├── mdx-options.ts │ │ │ └── schema.ts │ ├── test │ │ ├── fixtures │ │ │ ├── folder │ │ │ │ └── test.mdx │ │ │ ├── index-async.output.js │ │ │ ├── index-sync.output.js │ │ │ └── index.mdx │ │ └── index.test.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── openapi │ ├── CHANGELOG.md │ ├── css │ │ └── preset.css │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── build-routes.ts │ │ ├── generate-file.ts │ │ ├── generate.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── media │ │ │ └── adapter.ts │ │ ├── playground │ │ │ ├── auth │ │ │ │ └── oauth-dialog.tsx │ │ │ ├── client.tsx │ │ │ ├── fetcher.ts │ │ │ ├── get-default-values.ts │ │ │ ├── index.tsx │ │ │ ├── inputs.tsx │ │ │ ├── schema.tsx │ │ │ └── status-info.tsx │ │ ├── render │ │ │ ├── api-page.tsx │ │ │ ├── codeblock.tsx │ │ │ ├── heading.tsx │ │ │ ├── markdown.tsx │ │ │ ├── operation │ │ │ │ ├── api-example.tsx │ │ │ │ ├── get-request-data.ts │ │ │ │ └── index.tsx │ │ │ ├── renderer.tsx │ │ │ └── schema.tsx │ │ ├── requests │ │ │ ├── _shared.ts │ │ │ ├── curl.ts │ │ │ ├── go.ts │ │ │ ├── index.ts │ │ │ ├── javascript.ts │ │ │ └── python.ts │ │ ├── scalar │ │ │ ├── client.tsx │ │ │ └── index.tsx │ │ ├── server │ │ │ ├── create-method.ts │ │ │ ├── create.tsx │ │ │ ├── index.ts │ │ │ ├── proxy.ts │ │ │ └── source-api.tsx │ │ ├── types.ts │ │ ├── ui │ │ │ ├── client.tsx │ │ │ ├── components │ │ │ │ ├── accordion.tsx │ │ │ │ ├── dialog.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── method-label.tsx │ │ │ │ └── select.tsx │ │ │ ├── contexts │ │ │ │ ├── api.tsx │ │ │ │ └── code-example.tsx │ │ │ ├── index.tsx │ │ │ ├── lazy.tsx │ │ │ ├── select-tabs.tsx │ │ │ └── server-select.tsx │ │ └── utils │ │ │ ├── combine-schema.ts │ │ │ ├── generate-document.ts │ │ │ ├── get-pathname-from-input.ts │ │ │ ├── get-typescript-schema.ts │ │ │ ├── id-to-title.ts │ │ │ ├── input-to-string.ts │ │ │ ├── process-document.ts │ │ │ ├── schema-to-string.ts │ │ │ ├── schema.ts │ │ │ ├── server-url.ts │ │ │ └── use-query.ts │ ├── test │ │ ├── code-sample.test.ts │ │ ├── fixtures │ │ │ ├── museum.yaml │ │ │ ├── petstore.yaml │ │ │ ├── products.yaml │ │ │ └── unkey.json │ │ ├── index.test.ts │ │ ├── out │ │ │ ├── museum │ │ │ │ ├── events.mdx │ │ │ │ ├── operations.mdx │ │ │ │ └── tickets.mdx │ │ │ ├── petstore.mdx │ │ │ ├── samples │ │ │ │ ├── 1.bash │ │ │ │ ├── 1.go │ │ │ │ ├── 1.js │ │ │ │ └── 1.py │ │ │ └── unkey │ │ │ │ ├── apis.mdx │ │ │ │ ├── keys.mdx │ │ │ │ ├── liveness.mdx │ │ │ │ ├── migrations.mdx │ │ │ │ └── ratelimits.mdx │ │ └── utils.test.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── types.d.ts │ └── vitest.config.ts ├── python │ ├── CHANGELOG.md │ ├── eslint.config.mjs │ ├── fumapy │ │ ├── __init__.py │ │ └── mksource │ │ │ ├── __init__.py │ │ │ ├── document_module.py │ │ │ ├── json_encoder.py │ │ │ ├── models.py │ │ │ ├── simplify_docstring.py │ │ │ └── utils.py │ ├── package.json │ ├── pyproject.toml │ ├── src │ │ ├── components │ │ │ └── index.tsx │ │ ├── convert.ts │ │ ├── generated.ts │ │ ├── index.ts │ │ └── write.ts │ ├── styles │ │ └── preset.css │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── tsconfig │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json ├── twoslash │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── cache-fs.ts │ │ ├── index.ts │ │ └── ui │ │ │ ├── cn.ts │ │ │ ├── index.ts │ │ │ ├── popover.tsx │ │ │ └── popup.tsx │ ├── styles │ │ └── twoslash.css │ ├── tsconfig.json │ └── tsup.config.ts ├── typescript │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── create-project.ts │ │ ├── index.ts │ │ ├── lib │ │ │ ├── base.ts │ │ │ ├── cache.ts │ │ │ ├── file.ts │ │ │ ├── mdx.ts │ │ │ ├── remark-auto-type-table.tsx │ │ │ └── type-table.ts │ │ ├── markdown.ts │ │ └── ui │ │ │ ├── auto-type-table.tsx │ │ │ └── index.ts │ ├── test │ │ ├── fixtures │ │ │ ├── test-2.ts │ │ │ ├── test.mdx │ │ │ ├── test.output.js │ │ │ ├── test.output.json │ │ │ └── test.ts │ │ ├── index.test.ts │ │ └── type-gen.test.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts └── ui │ ├── CHANGELOG.md │ ├── README.md │ ├── css │ ├── black.css │ ├── catppuccin.css │ ├── dusk.css │ ├── neutral.css │ ├── ocean.css │ ├── preset.css │ ├── purple.css │ ├── shadcn.css │ ├── shiki.css │ ├── style.css │ └── vitepress.css │ ├── eslint.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── _registry │ │ ├── index.ts │ │ └── layout │ │ │ ├── docs-min.tsx │ │ │ └── page-min.tsx │ ├── components │ │ ├── accordion.tsx │ │ ├── banner.tsx │ │ ├── callout.tsx │ │ ├── card.tsx │ │ ├── codeblock.tsx │ │ ├── dialog │ │ │ ├── search-algolia.tsx │ │ │ ├── search-default.tsx │ │ │ ├── search-orama.tsx │ │ │ └── search.tsx │ │ ├── dynamic-codeblock.tsx │ │ ├── files.tsx │ │ ├── github-info.tsx │ │ ├── heading.tsx │ │ ├── image-zoom.css │ │ ├── image-zoom.tsx │ │ ├── inline-toc.tsx │ │ ├── layout │ │ │ ├── language-toggle.tsx │ │ │ ├── root-toggle.tsx │ │ │ ├── search-toggle.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── theme-toggle.tsx │ │ │ ├── toc-clerk.tsx │ │ │ ├── toc-thumb.tsx │ │ │ └── toc.tsx │ │ ├── steps.tsx │ │ ├── tabs.tsx │ │ ├── type-table.tsx │ │ └── ui │ │ │ ├── button.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── navigation-menu.tsx │ │ │ ├── popover.tsx │ │ │ ├── scroll-area.tsx │ │ │ └── tabs.tsx │ ├── contexts │ │ ├── i18n.tsx │ │ ├── layout.tsx │ │ ├── search.tsx │ │ ├── sidebar.tsx │ │ └── tree.tsx │ ├── i18n.tsx │ ├── icons.tsx │ ├── layouts │ │ ├── docs-client.tsx │ │ ├── docs.tsx │ │ ├── docs │ │ │ ├── page-client.tsx │ │ │ ├── page.tsx │ │ │ └── shared.tsx │ │ ├── home.tsx │ │ ├── home │ │ │ ├── menu.tsx │ │ │ └── navbar.tsx │ │ ├── links.tsx │ │ ├── notebook-client.tsx │ │ ├── notebook.tsx │ │ └── shared.tsx │ ├── mdx.server.tsx │ ├── mdx.tsx │ ├── og.tsx │ ├── page.server.tsx │ ├── page.tsx │ ├── provider │ │ ├── base.tsx │ │ └── index.tsx │ ├── theme │ │ └── typography │ │ │ ├── LICENSE │ │ │ ├── index.ts │ │ │ └── styles.ts │ └── utils │ │ ├── cn.ts │ │ ├── get-sidebar-tabs.tsx │ │ ├── is-active.ts │ │ ├── merge-refs.ts │ │ └── use-copy-button.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.mjs ├── renovate.json ├── tsconfig.json ├── turbo.json └── vitest.workspace.ts /.changeset/common-owls-relax.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'fumadocs-mdx': patch 3 | --- 4 | 5 | Support `outDir` option on `createMDX()` 6 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [["fumadocs-core", "fumadocs-ui", "create-fumadocs-app"]], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "dev", 9 | "updateInternalDependencies": "patch", 10 | "privatePackages": { "version": false }, 11 | "bumpVersionsWithWorkspaceProtocolOnly": true, 12 | "ignore": ["docs", "example-*"] 13 | } 14 | -------------------------------------------------------------------------------- /.changeset/curly-frogs-cheat.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'fumadocs-core': patch 3 | 'fumadocs-ui': patch 4 | --- 5 | 6 | Move `hide-if-empty` component to Fumadocs Core 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: fuma-nama 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions? 4 | url: https://github.com/fuma-nama/fumadocs/discussions 5 | about: Ask your questions here. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | .map.ts 9 | 10 | # testing 11 | /coverage 12 | 13 | # outputs 14 | .turbo 15 | .next 16 | dist 17 | .contentlayer 18 | .eslintcache 19 | .source 20 | 21 | # production 22 | /build 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/fumadocs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | pnpm-lock.yaml 3 | .changeset/*.md 4 | .github/**/*.md 5 | 6 | packages/create-app/versions.json 7 | 8 | apps/docs/shaders/* 9 | 10 | **/test/out/**/* 11 | **/test/fixtures/*.output.* 12 | 13 | out 14 | .content-collections 15 | 16 | apps/docs/content/docs/ui/museum/*.mdx 17 | !apps/docs/content/docs/ui/museum/index.mdx 18 | 19 | examples/openapi/content/docs/(api)/**/*.mdx 20 | !examples/openapi/content/docs/(api)/index.mdx 21 | 22 | apps/docs/content/docs/ui/typescript.mdx 23 | 24 | .content-collections/**/* 25 | 26 | packages/mdx-remote/test/fixtures/**/*.js 27 | packages/mdx-remote/test/fixtures/**/*.json 28 | build/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "mode": "auto" 5 | } 6 | ], 7 | "tailwindCSS.experimental.classRegex": [ 8 | ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], 9 | ["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] 10 | ], 11 | "files.associations": { 12 | "**/packages/create-app/template/**/*": "plaintext" 13 | }, 14 | "editor.defaultFormatter": "esbenp.prettier-vscode" 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![banner](./apps/docs/public/banner.png) 2 | 3 | The framework for building documentation websites in Next.js. 4 | 5 | 📘 Learn More: [Documentation](https://fumadocs.vercel.app). 6 | 7 | ## Compatibility 8 | 9 | All packages are **ESM only**. 10 | 11 | ## Sticker 12 | 13 | ![logo](./documents/logo.png) 14 | 15 | Welcome to print it out :D 16 | 17 | ## Contributions 18 | 19 | Make sure to read the [Contributing Guide](/.github/contributing.md) before submitting a pull request. 20 | -------------------------------------------------------------------------------- /apps/docs/.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_ORAMA_ENDPOINT=https://cloud.orama.run/v1/indexes/docs-dev-av3ifc 2 | NEXT_PUBLIC_ORAMA_API_KEY=jlm5Ab3P6RSkMIBNDFJw1Zt7bJbVgRcv 3 | 4 | ORAMA_INDEX_ID=hd6arq5e816mxigo5m6nakj1 5 | # To deploy search indexes to Orama, put private keys in .env.local: 6 | # ORAMA_PRIVATE_API_KEY= 7 | -------------------------------------------------------------------------------- /apps/docs/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | content/docs/openapi/**/*.mdx 3 | !content/docs/openapi/index.mdx 4 | 5 | public/registry 6 | -------------------------------------------------------------------------------- /apps/docs/app/(home)/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/app/(home)/arch.png -------------------------------------------------------------------------------- /apps/docs/app/(home)/blog/test/page.mdx: -------------------------------------------------------------------------------- 1 | export { withArticle as default } from 'fumadocs-ui/page'; 2 | 3 | ## Hello World 4 | 5 | Create a `page.mdx` file, put the page under a non-docs layout. 6 | 7 | Apply typography styles with: 8 | 9 | ```jsx 10 | export { withArticle as default } from 'fumadocs-ui/page'; 11 | ``` 12 | 13 | Done! 14 | -------------------------------------------------------------------------------- /apps/docs/app/(home)/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/app/(home)/main.png -------------------------------------------------------------------------------- /apps/docs/app/(home)/openapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/app/(home)/openapi.png -------------------------------------------------------------------------------- /apps/docs/app/(home)/showcase/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/app/(home)/showcase/design.png -------------------------------------------------------------------------------- /apps/docs/app/api/proxy/route.ts: -------------------------------------------------------------------------------- 1 | import { openapi } from '@/lib/source'; 2 | 3 | export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy(); 4 | -------------------------------------------------------------------------------- /apps/docs/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/app/icon.png -------------------------------------------------------------------------------- /apps/docs/app/llms-full.txt/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { getLLMText } from '@/lib/get-llm-text'; 3 | 4 | export const revalidate = false; 5 | 6 | export async function GET() { 7 | const scan = source 8 | .getPages() 9 | .filter((file) => file.slugs[0] !== 'openapi') 10 | .map(getLLMText); 11 | const scanned = await Promise.all(scan); 12 | 13 | return new Response(scanned.join('\n\n')); 14 | } 15 | -------------------------------------------------------------------------------- /apps/docs/app/llms.mdx/[...slug]/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from 'next/server'; 2 | import { getLLMText } from '@/lib/get-llm-text'; 3 | import { source } from '@/lib/source'; 4 | import { notFound } from 'next/navigation'; 5 | 6 | export const revalidate = false; 7 | 8 | export async function GET( 9 | _req: NextRequest, 10 | { params }: { params: Promise<{ slug: string[] }> }, 11 | ) { 12 | const slug = (await params).slug; 13 | const page = source.getPage(slug); 14 | if (!page) notFound(); 15 | 16 | return new NextResponse(await getLLMText(page)); 17 | } 18 | 19 | export function generateStaticParams() { 20 | return source.generateParams(); 21 | } 22 | -------------------------------------------------------------------------------- /apps/docs/app/llms.txt/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | 3 | export const revalidate = false; 4 | 5 | export async function GET() { 6 | const scanned: string[] = []; 7 | scanned.push('# Docs'); 8 | const map = new Map(); 9 | 10 | for (const page of source.getPages()) { 11 | const dir = page.slugs[0]; 12 | const list = map.get(dir) ?? []; 13 | list.push(`- [${page.data.title}](${page.url}): ${page.data.description}`); 14 | map.set(dir, list); 15 | } 16 | 17 | for (const [key, value] of map) { 18 | scanned.push(`## ${key}`); 19 | scanned.push(value.join('\n')); 20 | } 21 | 22 | return new Response(scanned.join('\n\n')); 23 | } 24 | -------------------------------------------------------------------------------- /apps/docs/app/og/[...slug]/JetBrainsMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/app/og/[...slug]/JetBrainsMono-Bold.ttf -------------------------------------------------------------------------------- /apps/docs/app/og/[...slug]/JetBrainsMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/app/og/[...slug]/JetBrainsMono-Regular.ttf -------------------------------------------------------------------------------- /apps/docs/app/static.json/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { source } from '@/lib/source'; 3 | import type { OramaDocument } from 'fumadocs-core/search/orama-cloud'; 4 | 5 | export const revalidate = false; 6 | 7 | export async function GET(): Promise { 8 | const pages = source.getPages(); 9 | const results = await Promise.all( 10 | pages.map(async (page) => { 11 | const { structuredData } = await page.data.load(); 12 | 13 | return { 14 | id: page.url, 15 | structured: structuredData, 16 | tag: page.slugs[0], 17 | url: page.url, 18 | title: page.data.title, 19 | description: page.data.description, 20 | } satisfies OramaDocument; 21 | }), 22 | ); 23 | 24 | return NextResponse.json(results); 25 | } 26 | -------------------------------------------------------------------------------- /apps/docs/components/ai/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { type ButtonHTMLAttributes, useState } from 'react'; 3 | import dynamic from 'next/dynamic'; 4 | 5 | // lazy load the dialog 6 | const SearchAI = dynamic(() => import('./search'), { ssr: false }); 7 | 8 | /** 9 | * The trigger component for AI search dialog. 10 | * 11 | * Use it like a normal button component. 12 | */ 13 | export function AISearchTrigger( 14 | props: ButtonHTMLAttributes, 15 | ) { 16 | const [open, setOpen] = useState(); 17 | 18 | return ( 19 | <> 20 | {open !== undefined ? ( 21 | 22 | ) : null} 23 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /apps/docs/content/shared/page-conventions.i18n.mdx: -------------------------------------------------------------------------------- 1 | You can add Markdown/meta files for different languages by attending `.{locale}` to your file name, like `page.cn.md` and `meta.cn.json`. 2 | 3 | Make sure to create a file for the default locale first, the locale code is optional (e.g. both `get-started.mdx` and `get-started.en.mdx` are accepted). 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/docs/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import next from 'eslint-config-custom/next.js'; 2 | 3 | export default [ 4 | { 5 | ignores: [ 6 | 'dist', 7 | 'node_modules', 8 | '.next/', 9 | '.source/', 10 | 'out/', 11 | 'next.config.mjs', 12 | 'postcss.config.js', 13 | ], 14 | }, 15 | ...next, 16 | { 17 | rules: { 18 | 'no-console': 'off', 19 | // for Fumadocs CLI 20 | 'import/no-relative-packages': 'off', 21 | }, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /apps/docs/lib/cn.ts: -------------------------------------------------------------------------------- 1 | export { twMerge as cn } from 'tailwind-merge'; 2 | -------------------------------------------------------------------------------- /apps/docs/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultMdxComponents from 'fumadocs-ui/mdx'; 2 | import * as FilesComponents from 'fumadocs-ui/components/files'; 3 | import * as TabsComponents from 'fumadocs-ui/components/tabs'; 4 | import type { MDXComponents } from 'mdx/types'; 5 | import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; 6 | import * as icons from 'lucide-react'; 7 | 8 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 9 | return { 10 | ...(icons as unknown as MDXComponents), 11 | ...defaultMdxComponents, 12 | ...TabsComponents, 13 | ...FilesComponents, 14 | Accordion, 15 | Accordions, 16 | ...components, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /apps/docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /apps/docs/public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/banner.png -------------------------------------------------------------------------------- /apps/docs/public/blog/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/blog/img.png -------------------------------------------------------------------------------- /apps/docs/public/blog/links-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/blog/links-menu.png -------------------------------------------------------------------------------- /apps/docs/public/blog/toc-popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/blog/toc-popover.png -------------------------------------------------------------------------------- /apps/docs/public/blog/v12-root-toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/blog/v12-root-toggle.png -------------------------------------------------------------------------------- /apps/docs/public/blog/v12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/blog/v12.png -------------------------------------------------------------------------------- /apps/docs/public/blog/v14-navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/blog/v14-navbar.png -------------------------------------------------------------------------------- /apps/docs/public/content-collections.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/content-collections.webp -------------------------------------------------------------------------------- /apps/docs/public/docs/docs-nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/docs-nav.png -------------------------------------------------------------------------------- /apps/docs/public/docs/nav-layout-docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/nav-layout-docs.png -------------------------------------------------------------------------------- /apps/docs/public/docs/nav-layout-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/nav-layout-home.png -------------------------------------------------------------------------------- /apps/docs/public/docs/nav-layout-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/nav-layout-menu.png -------------------------------------------------------------------------------- /apps/docs/public/docs/notebook-nav-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/notebook-nav-mode.png -------------------------------------------------------------------------------- /apps/docs/public/docs/notebook-tab-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/notebook-tab-mode.png -------------------------------------------------------------------------------- /apps/docs/public/docs/notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/notebook.png -------------------------------------------------------------------------------- /apps/docs/public/docs/sidebar-tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/sidebar-tabs.png -------------------------------------------------------------------------------- /apps/docs/public/docs/sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/sidebar.png -------------------------------------------------------------------------------- /apps/docs/public/docs/webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/docs/webhook.png -------------------------------------------------------------------------------- /apps/docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/logo.png -------------------------------------------------------------------------------- /apps/docs/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | 4 | Host: https://fumadocs.dev 5 | 6 | Sitemap: https://fumadocs.dev/sitemap.xml 7 | -------------------------------------------------------------------------------- /apps/docs/public/showcases/arktype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/arktype.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/assistant-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/assistant-ui.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/better-auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/better-auth.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/codehike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/codehike.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/dokploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/dokploy.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/expostarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/expostarter.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/hexta-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/hexta-ui.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/hiro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/hiro.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/million.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/million.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/mix-space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/mix-space.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/next-faq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/next-faq.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/nuqs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/nuqs.jpg -------------------------------------------------------------------------------- /apps/docs/public/showcases/openpanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/openpanel.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/sunar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/sunar.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/supastarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/supastarter.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/turbostarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/turbostarter.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/vision-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/vision-ui.png -------------------------------------------------------------------------------- /apps/docs/public/showcases/zen-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/showcases/zen-browser.png -------------------------------------------------------------------------------- /apps/docs/public/source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/source.png -------------------------------------------------------------------------------- /apps/docs/public/spot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/spot.png -------------------------------------------------------------------------------- /apps/docs/public/themes/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/themes/black.png -------------------------------------------------------------------------------- /apps/docs/public/themes/catppuccin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/themes/catppuccin.png -------------------------------------------------------------------------------- /apps/docs/public/themes/dusk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/themes/dusk.png -------------------------------------------------------------------------------- /apps/docs/public/themes/neutral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/themes/neutral.png -------------------------------------------------------------------------------- /apps/docs/public/themes/ocean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/themes/ocean.png -------------------------------------------------------------------------------- /apps/docs/public/themes/purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/themes/purple.png -------------------------------------------------------------------------------- /apps/docs/public/themes/vitepress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/apps/docs/public/themes/vitepress.png -------------------------------------------------------------------------------- /apps/docs/scripts/build-registry.mts: -------------------------------------------------------------------------------- 1 | import { build, writeOutput } from '@fumadocs/cli/build'; 2 | import { registry } from '@/components/registry.mjs'; 3 | 4 | export async function buildRegistry() { 5 | const out = await build(registry); 6 | 7 | await writeOutput('public/registry', out, { 8 | cleanDir: true, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /apps/docs/scripts/post-build.mts: -------------------------------------------------------------------------------- 1 | import env from '@next/env'; 2 | import { updateSearchIndexes } from './update-orama-index.mjs'; 3 | 4 | env.loadEnvConfig(process.cwd()); 5 | 6 | async function main() { 7 | await Promise.all([updateSearchIndexes()]); 8 | } 9 | 10 | await main().catch((e) => { 11 | console.error('Failed to run post build script', e); 12 | }); 13 | -------------------------------------------------------------------------------- /apps/docs/scripts/pre-build.mts: -------------------------------------------------------------------------------- 1 | import { buildRegistry } from '@/scripts/build-registry.mjs'; 2 | import * as OpenAPI from 'fumadocs-openapi'; 3 | import { rimraf } from 'rimraf'; 4 | 5 | export async function generateDocs() { 6 | await rimraf('./content/docs/openapi/(generated)'); 7 | 8 | await Promise.all([ 9 | OpenAPI.generateFiles({ 10 | input: ['./scalar.yaml'], 11 | output: './content/docs/openapi/(generated)', 12 | per: 'operation', 13 | includeDescription: true, 14 | }), 15 | ]); 16 | } 17 | 18 | async function main() { 19 | await Promise.all([generateDocs(), buildRegistry()]); 20 | } 21 | 22 | await main().catch((e) => { 23 | console.error('Failed to run pre build script', e); 24 | }); 25 | -------------------------------------------------------------------------------- /apps/docs/scripts/update-orama-index.mts: -------------------------------------------------------------------------------- 1 | import { sync, type OramaDocument } from 'fumadocs-core/search/orama-cloud'; 2 | import * as fs from 'node:fs/promises'; 3 | import { CloudManager } from '@oramacloud/client'; 4 | 5 | export async function updateSearchIndexes(): Promise { 6 | const apiKey = process.env.ORAMA_PRIVATE_API_KEY; 7 | 8 | if (!apiKey) { 9 | console.log('no api key for Orama found, skipping'); 10 | return; 11 | } 12 | 13 | const content = await fs.readFile('.next/server/app/static.json.body'); 14 | const records = JSON.parse(content.toString()) as OramaDocument[]; 15 | 16 | const manager = new CloudManager({ api_key: apiKey }); 17 | 18 | await sync(manager, { 19 | index: 'twr98yz9itca86121ukrqber', 20 | documents: records, 21 | }); 22 | 23 | console.log(`search updated: ${records.length} records`); 24 | } 25 | -------------------------------------------------------------------------------- /apps/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/nextjs.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./*"] 6 | } 7 | }, 8 | "include": [ 9 | "next-env.d.ts", 10 | "**/*.ts", 11 | "**/*.mts", 12 | "**/*.tsx", 13 | ".next/types/**/*.ts", 14 | "next.config.js", 15 | "tailwind.config.js" 16 | ], 17 | "exclude": ["node_modules", "eslint.config.mjs"] 18 | } 19 | -------------------------------------------------------------------------------- /documents/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/documents/logo.png -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | The apps here are used to generate code examples for our docs. 4 | 5 | Please be careful. 6 | -------------------------------------------------------------------------------- /examples/content-collections/.gitignore: -------------------------------------------------------------------------------- 1 | .content-collections -------------------------------------------------------------------------------- /examples/content-collections/app/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { HomeLayout } from 'fumadocs-ui/layouts/home'; 3 | import { baseOptions } from '@/app/layout.config'; 4 | 5 | export default function Layout({ children }: { children: ReactNode }) { 6 | return {children}; 7 | } 8 | -------------------------------------------------------------------------------- /examples/content-collections/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createFromSource } from 'fumadocs-core/search/server'; 3 | 4 | export const { GET } = createFromSource(source); 5 | -------------------------------------------------------------------------------- /examples/content-collections/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { DocsLayout } from 'fumadocs-ui/layouts/docs'; 3 | import type { ReactNode } from 'react'; 4 | import { baseOptions } from '@/app/layout.config'; 5 | 6 | export default function Layout({ children }: { children: ReactNode }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /examples/content-collections/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; 2 | 3 | export const baseOptions: BaseLayoutProps = { 4 | nav: { 5 | title: 'My App', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /examples/content-collections/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { RootProvider } from 'fumadocs-ui/provider'; 2 | import 'fumadocs-ui/style.css'; 3 | import { Inter } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | 6 | const inter = Inter({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | export default function Layout({ children }: { children: ReactNode }) { 11 | return ( 12 | 13 | 20 | {children} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /examples/content-collections/content-collections.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, defineConfig } from '@content-collections/core'; 2 | import { 3 | createMetaSchema, 4 | createDocSchema, 5 | transformMDX, 6 | } from '@fumadocs/content-collections/configuration'; 7 | 8 | const docs = defineCollection({ 9 | name: 'docs', 10 | directory: 'content/docs', 11 | include: '**/*.mdx', 12 | schema: createDocSchema, 13 | transform: transformMDX, 14 | }); 15 | 16 | const metas = defineCollection({ 17 | name: 'meta', 18 | directory: 'content/docs', 19 | include: '**/meta.json', 20 | parser: 'json', 21 | schema: createMetaSchema, 22 | }); 23 | 24 | export default defineConfig({ 25 | collections: [docs, metas], 26 | }); 27 | -------------------------------------------------------------------------------- /examples/content-collections/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | description: Your first document 4 | --- 5 | 6 | Hey there! 7 | 8 | Hello World 9 | 10 | ## Heading 11 | 12 | 13 | 14 | 15 | 16 | 17 | ### Heading 18 | 19 | #### Heading 20 | 21 | | name | description | 22 | | ----------- | ----------- | 23 | | Hello World | Goodbye | 24 | -------------------------------------------------------------------------------- /examples/content-collections/content/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | description: Your first document 4 | --- 5 | 6 | Hey there! 7 | 8 | ## Heading 9 | 10 | 11 | 12 | 13 | 14 | 15 | ### Heading 16 | 17 | ```js 18 | console.log('Hello World'); 19 | ``` 20 | 21 | #### Heading 22 | -------------------------------------------------------------------------------- /examples/content-collections/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { allDocs, allMetas } from 'content-collections'; 2 | import { loader } from 'fumadocs-core/source'; 3 | import { createMDXSource } from '@fumadocs/content-collections'; 4 | 5 | export const source = loader({ 6 | baseUrl: '/docs', 7 | source: createMDXSource(allDocs, allMetas), 8 | }); 9 | -------------------------------------------------------------------------------- /examples/content-collections/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultMdxComponents from 'fumadocs-ui/mdx'; 2 | import type { MDXComponents } from 'mdx/types'; 3 | 4 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 5 | return { 6 | ...defaultMdxComponents, 7 | ...components, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /examples/content-collections/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { withContentCollections } from '@content-collections/next'; 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const config = { 5 | reactStrictMode: true, 6 | }; 7 | 8 | export default withContentCollections(config); 9 | -------------------------------------------------------------------------------- /examples/content-collections/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-content-collections", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "next dev", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@fumadocs/content-collections": "workspace:*", 12 | "fumadocs-core": "workspace:*", 13 | "fumadocs-ui": "workspace:*", 14 | "next": "^15.3.3", 15 | "react": "19.1.0", 16 | "react-dom": "19.1.0" 17 | }, 18 | "devDependencies": { 19 | "@content-collections/core": "^0.9.0", 20 | "@content-collections/mdx": "^0.2.2", 21 | "@content-collections/next": "^0.2.6", 22 | "@types/mdx": "^2.0.13", 23 | "@types/react": "^19.1.6", 24 | "@types/react-dom": "^19.1.5", 25 | "typescript": "^5.8.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/i18n/app/[lang]/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { HomeLayout } from 'fumadocs-ui/layouts/home'; 3 | import { baseOptions } from '@/app/layout.config'; 4 | 5 | export default async function Layout({ 6 | params, 7 | children, 8 | }: { 9 | params: Promise<{ lang: string }>; 10 | children: ReactNode; 11 | }) { 12 | const { lang } = await params; 13 | 14 | return {children}; 15 | } 16 | -------------------------------------------------------------------------------- /examples/i18n/app/[lang]/docs-og/[...slug]/route.ts: -------------------------------------------------------------------------------- 1 | import { metadataImage } from '@/lib/metadata'; 2 | import { generateOGImage } from 'fumadocs-ui/og'; 3 | 4 | export const GET = metadataImage.createAPI((page) => { 5 | return generateOGImage({ 6 | title: page.data.title, 7 | description: page.data.description, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/i18n/app/[lang]/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { source } from '@/lib/source'; 3 | import { DocsLayout } from 'fumadocs-ui/layouts/docs'; 4 | import { baseOptions } from '@/app/layout.config'; 5 | 6 | export default async function Layout({ 7 | params, 8 | children, 9 | }: { 10 | params: Promise<{ lang: string }>; 11 | children: ReactNode; 12 | }) { 13 | const { lang } = await params; 14 | 15 | return ( 16 | 17 | {children} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/i18n/app/api/search/route-full.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createI18nSearchAPI } from 'fumadocs-core/search/server'; 3 | import { i18n } from '@/lib/i18n'; 4 | 5 | export const { GET } = createI18nSearchAPI('advanced', { 6 | i18n, 7 | indexes: source.getLanguages().flatMap(({ language, pages }) => 8 | pages.map((page) => ({ 9 | title: page.data.title, 10 | description: page.data.description, 11 | structuredData: page.data.structuredData, 12 | id: page.url, 13 | url: page.url, 14 | locale: language, 15 | })), 16 | ), 17 | }); 18 | -------------------------------------------------------------------------------- /examples/i18n/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createFromSource } from 'fumadocs-core/search/server'; 3 | import { createTokenizer } from '@orama/tokenizers/mandarin'; 4 | 5 | export const { GET } = createFromSource(source, { 6 | localeMap: { 7 | // you can customise search configs for specific locales, like: 8 | // [locale]: Orama options 9 | 10 | cn: { 11 | components: { 12 | tokenizer: createTokenizer(), 13 | }, 14 | search: { 15 | threshold: 0, 16 | tolerance: 0, 17 | }, 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /examples/i18n/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; 2 | import { i18n } from '@/lib/i18n'; 3 | 4 | export function baseOptions(locale: string): BaseLayoutProps { 5 | return { 6 | i18n, 7 | nav: { 8 | title: locale === 'cn' ? 'Chinese Docs' : 'English Docs', 9 | url: `/${locale}`, 10 | }, 11 | githubUrl: 'https://github.com', 12 | links: [ 13 | { 14 | type: 'main', 15 | text: locale === 'cn' ? '文檔' : 'Documentation', 16 | url: `/${locale}/docs`, 17 | }, 18 | ], 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /examples/i18n/content/docs/index.cn.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 中文 3 | description: 您的第一個文檔 4 | --- 5 | 6 | ## Hi 中文 7 | 8 | Fumadocs 對 i18n 有良好的支持 9 | -------------------------------------------------------------------------------- /examples/i18n/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | description: Your first document 4 | --- 5 | 6 | Hey there! 7 | 8 | ## Heading 9 | 10 | 11 | 12 | 13 | 14 | 15 | ### Heading 16 | 17 | #### Heading 18 | -------------------------------------------------------------------------------- /examples/i18n/content/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | description: Your first document 4 | --- 5 | 6 | Hey there! 7 | 8 | ## Heading 9 | 10 | 11 | 12 | 13 | 14 | 15 | ### Heading 16 | 17 | ```js 18 | console.log('Hello World'); 19 | ``` 20 | 21 | #### Heading 22 | -------------------------------------------------------------------------------- /examples/i18n/lib/i18n.ts: -------------------------------------------------------------------------------- 1 | import type { I18nConfig } from 'fumadocs-core/i18n'; 2 | 3 | export const i18n: I18nConfig = { 4 | defaultLanguage: 'en', 5 | languages: ['en', 'cn'], 6 | }; 7 | -------------------------------------------------------------------------------- /examples/i18n/lib/metadata.ts: -------------------------------------------------------------------------------- 1 | import { createMetadataImage } from 'fumadocs-core/server'; 2 | import { source } from '@/lib/source'; 3 | 4 | export const metadataImage = createMetadataImage({ 5 | source, 6 | }); 7 | -------------------------------------------------------------------------------- /examples/i18n/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { createMDXSource } from 'fumadocs-mdx'; 2 | import { loader } from 'fumadocs-core/source'; 3 | import { i18n } from '@/lib/i18n'; 4 | import { docs, meta } from '@/.source'; 5 | 6 | export const source = loader({ 7 | baseUrl: '/docs', 8 | source: createMDXSource(docs, meta), 9 | i18n, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/i18n/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultMdxComponents from 'fumadocs-ui/mdx'; 2 | import type { MDXComponents } from 'mdx/types'; 3 | 4 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 5 | return { 6 | ...defaultMdxComponents, 7 | ...components, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /examples/i18n/middleware.ts: -------------------------------------------------------------------------------- 1 | import { createI18nMiddleware } from 'fumadocs-core/i18n'; 2 | import { i18n } from '@/lib/i18n'; 3 | 4 | export default createI18nMiddleware(i18n); 5 | 6 | export const config = { 7 | // Matcher ignoring `/_next/` and `/api/` 8 | matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], 9 | }; 10 | -------------------------------------------------------------------------------- /examples/i18n/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { createMDX } from 'fumadocs-mdx/next'; 2 | 3 | const withMDX = createMDX(); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const config = { 7 | reactStrictMode: true, 8 | }; 9 | 10 | export default withMDX(config); 11 | -------------------------------------------------------------------------------- /examples/i18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-i18n", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "next dev --turbo", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@orama/tokenizers": "^3.1.6", 12 | "fumadocs-core": "workspace:*", 13 | "fumadocs-mdx": "workspace:*", 14 | "fumadocs-ui": "workspace:*", 15 | "next": "15.3.3", 16 | "react": "19.1.0", 17 | "react-dom": "19.1.0" 18 | }, 19 | "devDependencies": { 20 | "@types/mdx": "^2.0.13", 21 | "@types/react": "^19.1.6", 22 | "@types/react-dom": "^19.1.5", 23 | "typescript": "^5.8.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/i18n/source.config.ts: -------------------------------------------------------------------------------- 1 | import { defineDocs } from 'fumadocs-mdx/config'; 2 | 3 | export const { docs, meta } = defineDocs({ 4 | dir: 'content/docs', 5 | }); 6 | -------------------------------------------------------------------------------- /examples/i18n/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "paths": { 19 | "@/*": ["./*"] 20 | }, 21 | "plugins": [ 22 | { 23 | "name": "next" 24 | } 25 | ] 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /examples/next-mdx/app/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { HomeLayout } from 'fumadocs-ui/layouts/home'; 3 | import { baseOptions } from '@/app/layout.config'; 4 | 5 | export default function Layout({ 6 | children, 7 | }: { 8 | children: ReactNode; 9 | }): React.ReactElement { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /examples/next-mdx/app/api/search/route-full.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createSearchAPI } from 'fumadocs-core/search/server'; 3 | 4 | export const { GET } = createSearchAPI('advanced', { 5 | indexes: source.getPages().map((page) => ({ 6 | title: page.data.title, 7 | description: page.data.description, 8 | url: page.url, 9 | id: page.url, 10 | structuredData: page.data.structuredData, 11 | })), 12 | }); 13 | -------------------------------------------------------------------------------- /examples/next-mdx/app/api/search/route-tag.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createFromSource } from 'fumadocs-core/search/server'; 3 | 4 | export const { GET } = createFromSource(source, { 5 | buildIndex(page) { 6 | return { 7 | title: page.data.title, 8 | description: page.data.description, 9 | url: page.url, 10 | id: page.url, 11 | structuredData: page.data.structuredData, 12 | // use your desired value, like page.slugs[0] 13 | tag: '', 14 | }; 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /examples/next-mdx/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createFromSource } from 'fumadocs-core/search/server'; 3 | 4 | export const { GET } = createFromSource(source); 5 | -------------------------------------------------------------------------------- /examples/next-mdx/app/docs-og/[...slug]/route.tsx: -------------------------------------------------------------------------------- 1 | import { generateOGImage } from 'fumadocs-ui/og'; 2 | import { source } from '@/lib/source'; 3 | import { notFound } from 'next/navigation'; 4 | 5 | export async function GET( 6 | _req: Request, 7 | { params }: { params: Promise<{ slug: string[] }> }, 8 | ) { 9 | const { slug } = await params; 10 | const page = source.getPage(slug.slice(0, -1)); 11 | if (!page) notFound(); 12 | 13 | return generateOGImage({ 14 | title: page.data.title, 15 | description: page.data.description, 16 | site: 'My App', 17 | }); 18 | } 19 | 20 | export function generateStaticParams() { 21 | return source.generateParams().map((page) => ({ 22 | ...page, 23 | slug: [...page.slug, 'image.png'], 24 | })); 25 | } 26 | -------------------------------------------------------------------------------- /examples/next-mdx/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { DocsLayout } from 'fumadocs-ui/layouts/docs'; 3 | import type { ReactNode } from 'react'; 4 | import { baseOptions } from '@/app/layout.config'; 5 | 6 | export default function Layout({ children }: { children: ReactNode }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /examples/next-mdx/app/global.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | -------------------------------------------------------------------------------- /examples/next-mdx/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; 2 | 3 | export const baseOptions: BaseLayoutProps = { 4 | nav: { 5 | title: 'My App', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /examples/next-mdx/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { RootProvider } from 'fumadocs-ui/provider'; 2 | import './global.css'; 3 | import { Inter } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | 6 | const inter = Inter({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | export default function Layout({ children }: { children: ReactNode }) { 11 | return ( 12 | 13 | 20 | {children} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /examples/next-mdx/app/static.json/orama-cloud.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { type OramaDocument } from 'fumadocs-core/search/orama-cloud'; 3 | import { source } from '@/lib/source'; 4 | 5 | export const revalidate = false; 6 | 7 | export function GET() { 8 | const results: OramaDocument[] = []; 9 | 10 | for (const page of source.getPages()) { 11 | results.push({ 12 | id: page.url, 13 | structured: page.data.structuredData, 14 | url: page.url, 15 | title: page.data.title, 16 | description: page.data.description, 17 | }); 18 | } 19 | 20 | return NextResponse.json(results); 21 | } 22 | -------------------------------------------------------------------------------- /examples/next-mdx/content/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test 3 | description: A document to test Fumadocs 4 | --- 5 | 6 | Hey there! 7 | 8 | ## Cards 9 | 10 | 11 | 12 | 13 | 14 | 15 | ### CodeBlock 16 | 17 | ```js 18 | console.log('Hello World'); 19 | ``` 20 | 21 | #### List 22 | 23 | - Hello 24 | - World 25 | -------------------------------------------------------------------------------- /examples/next-mdx/content/docs/test2.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fumadocs 3 | description: You can just be minimal. 4 | --- 5 | 6 | ## Overview 7 | 8 | Fumadocs is a docs framework. 9 | -------------------------------------------------------------------------------- /examples/next-mdx/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { docs } from '@/.source'; 2 | import { loader } from 'fumadocs-core/source'; 3 | 4 | export const source = loader({ 5 | baseUrl: '/docs', 6 | source: docs.toFumadocsSource(), 7 | }); 8 | -------------------------------------------------------------------------------- /examples/next-mdx/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultMdxComponents from 'fumadocs-ui/mdx'; 2 | import type { MDXComponents } from 'mdx/types'; 3 | 4 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 5 | return { 6 | ...defaultMdxComponents, 7 | ...components, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /examples/next-mdx/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { createMDX } from 'fumadocs-mdx/next'; 2 | 3 | const withMDX = createMDX(); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const config = { 7 | reactStrictMode: true, 8 | }; 9 | 10 | export default withMDX(config); 11 | -------------------------------------------------------------------------------- /examples/next-mdx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-next-mdx", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "next dev --turbo", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "fumadocs-core": "workspace:*", 12 | "fumadocs-mdx": "workspace:*", 13 | "fumadocs-ui": "workspace:*", 14 | "next": "15.3.3", 15 | "react": "19.1.0", 16 | "react-dom": "19.1.0" 17 | }, 18 | "devDependencies": { 19 | "@tailwindcss/postcss": "^4.1.8", 20 | "@types/mdx": "^2.0.13", 21 | "@types/react": "^19.1.6", 22 | "@types/react-dom": "^19.1.5", 23 | "postcss": "^8.5.4", 24 | "tailwindcss": "^4.1.8", 25 | "typescript": "^5.8.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/next-mdx/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/next-mdx/source.config.ts: -------------------------------------------------------------------------------- 1 | import { defineDocs, defineConfig } from 'fumadocs-mdx/config'; 2 | 3 | export const docs = defineDocs({ 4 | dir: 'content/docs', 5 | }); 6 | 7 | export default defineConfig(); 8 | -------------------------------------------------------------------------------- /examples/next-mdx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "paths": { 19 | "@/*": ["./*"] 20 | }, 21 | "plugins": [ 22 | { 23 | "name": "next" 24 | } 25 | ] 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /examples/openapi/.gitignore: -------------------------------------------------------------------------------- 1 | content/docs/(api)/**/*.mdx 2 | -------------------------------------------------------------------------------- /examples/openapi/README.md: -------------------------------------------------------------------------------- 1 | # Unkey API Docs 2 | 3 | This is an example API documentation based on Unkey's [API documentation](https://github.com/unkeyed/unkey), using Fumadocs OpenAPI. 4 | -------------------------------------------------------------------------------- /examples/openapi/app/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { HomeLayout } from 'fumadocs-ui/layouts/home'; 3 | import { baseOptions } from '@/app/layout.config'; 4 | 5 | export default function Layout({ children }: { children: ReactNode }) { 6 | return {children}; 7 | } 8 | -------------------------------------------------------------------------------- /examples/openapi/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createFromSource } from 'fumadocs-core/search/server'; 3 | 4 | export const { GET } = createFromSource(source); 5 | -------------------------------------------------------------------------------- /examples/openapi/app/docs-og/[...slug]/route.ts: -------------------------------------------------------------------------------- 1 | import { generateOGImage } from 'fumadocs-ui/og'; 2 | import { source } from '@/lib/source'; 3 | import { notFound } from 'next/navigation'; 4 | 5 | export async function GET( 6 | _req: Request, 7 | { params }: { params: Promise<{ slug: string[] }> }, 8 | ) { 9 | const { slug } = await params; 10 | const page = source.getPage(slug.slice(0, -1)); 11 | if (!page) notFound(); 12 | 13 | return generateOGImage({ 14 | title: page.data.title, 15 | description: page.data.description, 16 | }); 17 | } 18 | 19 | export function generateStaticParams() { 20 | return source.generateParams().map((page) => ({ 21 | ...page, 22 | slug: [...page.slug, 'image.png'], 23 | })); 24 | } 25 | -------------------------------------------------------------------------------- /examples/openapi/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { DocsLayout } from 'fumadocs-ui/layouts/notebook'; 3 | import type { ReactNode } from 'react'; 4 | import { baseOptions } from '@/app/layout.config'; 5 | 6 | export default function RootDocsLayout({ children }: { children: ReactNode }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /examples/openapi/app/global.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | @import 'fumadocs-openapi/css/preset.css'; 5 | 6 | :root { 7 | --fd-layout-width: 1600px; 8 | } 9 | -------------------------------------------------------------------------------- /examples/openapi/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; 2 | import { Logo } from '@/app/logo'; 3 | 4 | export const baseOptions: BaseLayoutProps = { 5 | nav: { 6 | title: , 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /examples/openapi/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { RootProvider } from 'fumadocs-ui/provider'; 2 | import './global.css'; 3 | import { Inter, JetBrains_Mono } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | 6 | const inter = Inter({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | const mono = JetBrains_Mono({ 11 | subsets: ['latin'], 12 | variable: '--font-mono', 13 | }); 14 | 15 | export default function Layout({ children }: { children: ReactNode }) { 16 | return ( 17 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /examples/openapi/content/docs/auth.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Authentication' 3 | description: "How to authenticate your requests to Unkey's API" 4 | --- 5 | 6 | You'll need to authenticate your requests to access some of the endpoints in the Unkey API. In this guide, we'll look at how authentication works. 7 | 8 | ## Bearer Token 9 | 10 | When requesting resources, you will need your root key — you will find it in the [Dashboard](https://app.unkey.com/settings/root-keys). Here's how to add the root key to the request header using cURL: 11 | 12 | ```bash 13 | curl https://api.unkey.dev/v1/... \ 14 | -H "Authorization: Bearer unkey_xxx" 15 | ``` 16 | 17 | Always keep your root key safe and reset it if you suspect it has been compromised. 18 | -------------------------------------------------------------------------------- /examples/openapi/content/docs/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "---API Documentation---", 4 | "index", 5 | "...", 6 | "---References---", 7 | "...(api)" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /examples/openapi/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { createMDXSource } from 'fumadocs-mdx'; 2 | import { loader } from 'fumadocs-core/source'; 3 | import { attachFile, createOpenAPI } from 'fumadocs-openapi/server'; 4 | import { docs, meta } from '@/.source'; 5 | 6 | export const source = loader({ 7 | baseUrl: '/docs', 8 | source: createMDXSource(docs, meta), 9 | pageTree: { 10 | attachFile, 11 | }, 12 | }); 13 | 14 | export const openapi = createOpenAPI(); 15 | -------------------------------------------------------------------------------- /examples/openapi/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultMdxComponents from 'fumadocs-ui/mdx'; 2 | import type { MDXComponents } from 'mdx/types'; 3 | import { openapi } from '@/lib/source'; 4 | import { APIPage } from 'fumadocs-openapi/ui'; 5 | 6 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 7 | return { 8 | ...defaultMdxComponents, 9 | APIPage: (props) => , 10 | ...components, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /examples/openapi/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { createMDX } from 'fumadocs-mdx/next'; 2 | 3 | const withMDX = createMDX(); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const config = { 7 | reactStrictMode: true, 8 | }; 9 | 10 | export default withMDX(config); 11 | -------------------------------------------------------------------------------- /examples/openapi/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/openapi/scripts/generate-docs.mjs: -------------------------------------------------------------------------------- 1 | import * as OpenAPI from 'fumadocs-openapi'; 2 | import { rimraf } from 'rimraf'; 3 | 4 | const out = './content/docs/(api)'; 5 | 6 | async function generate() { 7 | // clean generated files 8 | await rimraf(out, { 9 | filter(v) { 10 | return !v.endsWith('index.mdx') && !v.endsWith('meta.json'); 11 | }, 12 | }); 13 | 14 | await OpenAPI.generateFiles({ 15 | // input files 16 | input: ['./openapi.json'], 17 | output: out, 18 | }); 19 | } 20 | 21 | void generate(); 22 | -------------------------------------------------------------------------------- /examples/openapi/source.config.ts: -------------------------------------------------------------------------------- 1 | import { defineDocs } from 'fumadocs-mdx/config'; 2 | 3 | export const { docs, meta } = defineDocs({ 4 | dir: 'content/docs', 5 | }); 6 | -------------------------------------------------------------------------------- /examples/openapi/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "paths": { 19 | "@/*": ["./*"] 20 | }, 21 | "plugins": [ 22 | { 23 | "name": "next" 24 | } 25 | ] 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /examples/python/.gitignore: -------------------------------------------------------------------------------- 1 | content/docs/httpx 2 | httpx.json -------------------------------------------------------------------------------- /examples/python/README.md: -------------------------------------------------------------------------------- 1 | 1. Use `pnpm python:generate` to generate JSON file locally using Python. 2 | 2. Use `pnpm build:docs` to generate docs 3 | -------------------------------------------------------------------------------- /examples/python/app/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { HomeLayout } from 'fumadocs-ui/layouts/home'; 3 | import { baseOptions } from '@/app/layout.config'; 4 | 5 | export default function Layout({ children }: { children: ReactNode }) { 6 | return {children}; 7 | } 8 | -------------------------------------------------------------------------------- /examples/python/app/(home)/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function HomePage() { 4 | return ( 5 |
6 |

Hello World

7 |

8 | You can open{' '} 9 | 13 | /docs 14 | {' '} 15 | and see the documentation. 16 |

17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /examples/python/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createFromSource } from 'fumadocs-core/search/server'; 3 | 4 | export const { GET } = createFromSource(source); 5 | -------------------------------------------------------------------------------- /examples/python/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DocsLayout } from 'fumadocs-ui/layouts/docs'; 2 | import type { ReactNode } from 'react'; 3 | import { baseOptions } from '@/app/layout.config'; 4 | import { source } from '@/lib/source'; 5 | 6 | export default function Layout({ children }: { children: ReactNode }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /examples/python/app/global.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | @import 'fumadocs-python/preset.css'; 5 | -------------------------------------------------------------------------------- /examples/python/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; 2 | 3 | /** 4 | * Shared layout configurations 5 | * 6 | * you can customise layouts individually from: 7 | * Home Layout: app/(home)/layout.tsx 8 | * Docs Layout: app/docs/layout.tsx 9 | */ 10 | export const baseOptions: BaseLayoutProps = { 11 | nav: { 12 | title: 'Python Docs', 13 | }, 14 | links: [ 15 | { 16 | text: 'Documentation', 17 | url: '/docs', 18 | active: 'nested-url', 19 | }, 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /examples/python/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './global.css'; 2 | import { RootProvider } from 'fumadocs-ui/provider'; 3 | import { Inter } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | 6 | const inter = Inter({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | export default function Layout({ children }: { children: ReactNode }) { 11 | return ( 12 | 13 | 14 | {children} 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/python/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | description: Your first document 4 | --- 5 | 6 | Welcome to the docs! You can start writing documents in `/content/docs`. 7 | 8 | ## What is Next? 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/python/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { docs } from '@/.source'; 2 | import { loader } from 'fumadocs-core/source'; 3 | 4 | // See https://fumadocs.vercel.app/docs/headless/source-api for more info 5 | export const source = loader({ 6 | // it assigns a URL to your pages 7 | baseUrl: '/docs', 8 | source: docs.toFumadocsSource(), 9 | }); 10 | -------------------------------------------------------------------------------- /examples/python/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultMdxComponents from 'fumadocs-ui/mdx'; 2 | import type { MDXComponents } from 'mdx/types'; 3 | import * as Python from 'fumadocs-python/components'; 4 | 5 | // use this function to get MDX components, you will need it for rendering MDX 6 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 7 | return { 8 | ...defaultMdxComponents, 9 | ...Python, 10 | ...components, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /examples/python/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { createMDX } from 'fumadocs-mdx/next'; 2 | 3 | const withMDX = createMDX(); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const config = { 7 | reactStrictMode: true, 8 | }; 9 | 10 | export default withMDX(config); 11 | -------------------------------------------------------------------------------- /examples/python/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/python/scripts/generate-docs.mjs: -------------------------------------------------------------------------------- 1 | import { rimraf } from 'rimraf'; 2 | import * as Python from 'fumadocs-python'; 3 | import * as fs from 'node:fs/promises'; 4 | 5 | async function generate() { 6 | const out = 'content/docs/httpx'; 7 | await rimraf(out); 8 | 9 | const content = JSON.parse((await fs.readFile('./httpx.json')).toString()); 10 | const converted = Python.convert(content, { 11 | baseUrl: '/docs', 12 | }).filter((file) => !file.path.startsWith('httpx/_transports')); 13 | 14 | await Python.write(converted, { 15 | outDir: out, 16 | }); 17 | } 18 | 19 | void generate(); 20 | -------------------------------------------------------------------------------- /examples/python/source.config.ts: -------------------------------------------------------------------------------- 1 | import { defineDocs, defineConfig } from 'fumadocs-mdx/config'; 2 | 3 | // Options: https://fumadocs.vercel.app/docs/mdx/collections#define-docs 4 | export const docs = defineDocs({ 5 | dir: 'content/docs', 6 | }); 7 | 8 | export default defineConfig({ 9 | mdxOptions: { 10 | // MDX options 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /examples/python/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "paths": { 19 | "@/.source": ["./.source/index.ts"], 20 | "@/*": ["./*"] 21 | }, 22 | "plugins": [ 23 | { 24 | "name": "next" 25 | } 26 | ] 27 | }, 28 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /examples/react-router/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | 4 | # React Router 5 | /.react-router/ 6 | /build/ 7 | -------------------------------------------------------------------------------- /examples/react-router/app/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | -------------------------------------------------------------------------------- /examples/react-router/app/docs/search.ts: -------------------------------------------------------------------------------- 1 | import type { Route } from './+types/search'; 2 | import { createSearchAPI } from 'fumadocs-core/search/server'; 3 | import { source } from '@/source'; 4 | import { structure } from 'fumadocs-core/mdx-plugins'; 5 | 6 | const server = createSearchAPI('advanced', { 7 | indexes: source.getPages().map((page) => ({ 8 | id: page.url, 9 | url: page.url, 10 | title: page.data.title ?? '', 11 | description: page.data.description, 12 | structuredData: structure(page.data.content), 13 | })), 14 | }); 15 | 16 | export async function loader({ request }: Route.LoaderArgs) { 17 | return server.GET(request); 18 | } 19 | -------------------------------------------------------------------------------- /examples/react-router/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { type RouteConfig, index, route } from '@react-router/dev/routes'; 2 | 3 | export default [ 4 | index('routes/home.tsx'), 5 | route('docs/*', 'docs/page.tsx'), 6 | route('api/search', 'docs/search.ts'), 7 | ] satisfies RouteConfig; 8 | -------------------------------------------------------------------------------- /examples/react-router/content/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test 3 | description: A document to test Fumadocs 4 | --- 5 | 6 | Hey there! 7 | 8 | ## Cards 9 | 10 | 11 | 12 | 13 | 14 | 15 | ### CodeBlock 16 | 17 | ```js 18 | console.log('Hello World'); 19 | ``` 20 | 21 | #### List 22 | 23 | - Hello 24 | - World 25 | -------------------------------------------------------------------------------- /examples/react-router/content/docs/test2.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fumadocs 3 | description: You can just be minimal. 4 | --- 5 | 6 | ## Overview 7 | 8 | Fumadocs is a docs framework. 9 | -------------------------------------------------------------------------------- /examples/react-router/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/examples/react-router/public/favicon.ico -------------------------------------------------------------------------------- /examples/react-router/react-router.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@react-router/dev/config'; 2 | import { source } from './app/source'; 3 | 4 | export default { 5 | ssr: true, 6 | async prerender({ getStaticPaths }) { 7 | return [...getStaticPaths(), ...source.getPages().map((page) => page.url)]; 8 | }, 9 | } satisfies Config; 10 | -------------------------------------------------------------------------------- /examples/react-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*", 4 | "**/.server/**/*", 5 | "**/.client/**/*", 6 | ".react-router/types/**/*" 7 | ], 8 | "compilerOptions": { 9 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 10 | "types": ["node", "vite/client"], 11 | "target": "esnext", 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "jsx": "react-jsx", 15 | "rootDirs": [".", "./.react-router/types"], 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./app/*"] 19 | }, 20 | "esModuleInterop": true, 21 | "verbatimModuleSyntax": true, 22 | "noEmit": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/react-router/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { reactRouter } from '@react-router/dev/vite'; 2 | import tailwindcss from '@tailwindcss/vite'; 3 | import { defineConfig } from 'vite'; 4 | import tsconfigPaths from 'vite-tsconfig-paths'; 5 | 6 | export default defineConfig({ 7 | build: { 8 | rollupOptions: { 9 | external: ['shiki'], 10 | }, 11 | }, 12 | plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], 13 | }); 14 | -------------------------------------------------------------------------------- /examples/remote-mdx/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DocsLayout } from 'fumadocs-ui/layouts/docs'; 2 | import type { ReactNode } from 'react'; 3 | import pageTree from '@/content/docs/page-tree'; 4 | 5 | export default async function Layout({ children }: { children: ReactNode }) { 6 | return ( 7 | 8 | {children} 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /examples/remote-mdx/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { RootProvider } from 'fumadocs-ui/provider'; 2 | import 'fumadocs-ui/style.css'; 3 | import { Inter } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | 6 | const inter = Inter({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | export default function Layout({ children }: { children: ReactNode }) { 11 | return ( 12 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /examples/remote-mdx/content/docs/page-tree.ts: -------------------------------------------------------------------------------- 1 | import { PageTree } from 'fumadocs-core/server'; 2 | 3 | export default { 4 | name: 'Docs', 5 | children: [ 6 | { 7 | type: 'page', 8 | name: 'Home', 9 | url: '/docs', 10 | }, 11 | { 12 | type: 'page', 13 | name: 'Comparisons', 14 | url: '/docs/comparisons', 15 | }, 16 | { 17 | type: 'page', 18 | name: 'Test', 19 | url: '/docs/test', 20 | }, 21 | ], 22 | } satisfies PageTree.Root; 23 | -------------------------------------------------------------------------------- /examples/remote-mdx/content/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test 3 | --- 4 | 5 | Hey! 6 | 7 | ```tsx 8 | 'use client'; 9 | 10 | import { useChat } from 'ai/react'; 11 | 12 | export default function Chat() { 13 | const { messages, input, handleInputChange, handleSubmit } = useChat({ 14 | maxSteps: 5, // [!code highlight] 15 | }); 16 | 17 | // ... rest of your component code 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/remote-mdx/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from 'next'; 2 | 3 | const config: NextConfig = { 4 | reactStrictMode: true, 5 | }; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /examples/remote-mdx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-remote-mdx", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "next dev", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@fumadocs/mdx-remote": "workspace:*", 12 | "fumadocs-core": "workspace:*", 13 | "fumadocs-ui": "workspace:*", 14 | "next": "15.3.3", 15 | "react": "19.1.0", 16 | "react-dom": "19.1.0", 17 | "tinyglobby": "^0.2.14" 18 | }, 19 | "devDependencies": { 20 | "@types/mdx": "^2.0.13", 21 | "@types/react": "^19.1.6", 22 | "@types/react-dom": "^19.1.5", 23 | "typescript": "^5.8.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/tanstack-start/.gitignore: -------------------------------------------------------------------------------- 1 | routeTree.gen.ts 2 | node_modules 3 | .output 4 | .vinxi -------------------------------------------------------------------------------- /examples/tanstack-start/app/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createStartAPIHandler, 3 | defaultAPIFileRouteHandler, 4 | } from '@tanstack/react-start/api'; 5 | 6 | export default createStartAPIHandler(defaultAPIFileRouteHandler); 7 | -------------------------------------------------------------------------------- /examples/tanstack-start/app/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | -------------------------------------------------------------------------------- /examples/tanstack-start/app/client.tsx: -------------------------------------------------------------------------------- 1 | import { hydrateRoot } from 'react-dom/client'; 2 | import { StartClient } from '@tanstack/react-start'; 3 | import { createRouter } from './router'; 4 | 5 | const router = createRouter(); 6 | 7 | hydrateRoot(document, ); 8 | -------------------------------------------------------------------------------- /examples/tanstack-start/app/router.tsx: -------------------------------------------------------------------------------- 1 | import { createRouter as createTanStackRouter } from '@tanstack/react-router'; 2 | import { routeTree } from './routeTree.gen'; 3 | 4 | export function createRouter() { 5 | return createTanStackRouter({ 6 | routeTree, 7 | scrollRestoration: true, 8 | }); 9 | } 10 | 11 | declare module '@tanstack/react-router' { 12 | interface Register { 13 | router: ReturnType; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/tanstack-start/app/routes/api/search.ts: -------------------------------------------------------------------------------- 1 | import { createAPIFileRoute } from '@tanstack/react-start/api'; 2 | import { createSearchAPI } from 'fumadocs-core/search/server'; 3 | import { source } from '../../../lib/source'; 4 | import { structure } from 'fumadocs-core/mdx-plugins'; 5 | 6 | const server = createSearchAPI('advanced', { 7 | indexes: source.getPages().map((page) => ({ 8 | id: page.url, 9 | url: page.url, 10 | title: page.data.title ?? '', 11 | description: page.data.description, 12 | structuredData: structure(page.data.content), 13 | })), 14 | }); 15 | 16 | export const APIRoute = createAPIFileRoute('/api/search')({ 17 | GET: ({ request }) => { 18 | return server.GET(request); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /examples/tanstack-start/app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute, Link } from '@tanstack/react-router'; 2 | import { HomeLayout } from 'fumadocs-ui/layouts/home'; 3 | 4 | export const Route = createFileRoute('/')({ 5 | component: Home, 6 | }); 7 | 8 | function Home() { 9 | return ( 10 | 16 |

Fumadocs on Tanstack Start.

17 | 21 | Open Docs 22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /examples/tanstack-start/app/ssr.tsx: -------------------------------------------------------------------------------- 1 | // app/ssr.tsx 2 | import { 3 | createStartHandler, 4 | defaultStreamHandler, 5 | } from '@tanstack/react-start/server'; 6 | import { getRouterManifest } from '@tanstack/react-start/router-manifest'; 7 | 8 | import { createRouter } from './router'; 9 | 10 | export default createStartHandler({ 11 | createRouter, 12 | getRouterManifest, 13 | })(defaultStreamHandler); 14 | -------------------------------------------------------------------------------- /examples/tanstack-start/content/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test 3 | description: A document to test Fumadocs 4 | --- 5 | 6 | Hey there! 7 | 8 | ## Cards 9 | 10 | 11 | 12 | 13 | 14 | 15 | ### CodeBlock 16 | 17 | ```js 18 | console.log('Hello World'); 19 | ``` 20 | 21 | #### List 22 | 23 | - Hello 24 | - World 25 | -------------------------------------------------------------------------------- /examples/tanstack-start/content/docs/test2.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fumadocs 3 | description: You can just be minimal. 4 | --- 5 | 6 | ## Overview 7 | 8 | Fumadocs is a docs framework. 9 | -------------------------------------------------------------------------------- /examples/tanstack-start/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "moduleResolution": "Bundler", 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "types": ["node", "vite/client"], 7 | "module": "ESNext", 8 | "target": "ESNext", 9 | "skipLibCheck": true, 10 | "strictNullChecks": true, 11 | "paths": { 12 | "@/*": ["./*"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/cli/.gitignore: -------------------------------------------------------------------------------- 1 | src/generated.js 2 | -------------------------------------------------------------------------------- /packages/cli/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/library.js'; 2 | 3 | export default [ 4 | { 5 | ignores: ['dist', 'node_modules', 'test/repo', 'test/repo-2'], 6 | }, 7 | ...library, 8 | { 9 | rules: { 10 | 'import/no-relative-packages': 'off', 11 | }, 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/cli/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const typescriptExtensions = ['.ts', '.tsx', '.js', '.jsx']; 2 | -------------------------------------------------------------------------------- /packages/cli/src/generated.d.ts: -------------------------------------------------------------------------------- 1 | import { type templates } from '../scripts/sync'; 2 | 3 | export const generated: typeof templates; 4 | -------------------------------------------------------------------------------- /packages/cli/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import { i18nPlugin } from '@/plugins/i18n'; 2 | import { openapiPlugin } from '@/plugins/openapi'; 3 | import { type Plugin } from '@/commands/init'; 4 | 5 | export const plugins: Record = { 6 | i18n: i18nPlugin, 7 | openapi: openapiPlugin, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/cli/src/utils/file-tree/run-tree.ts: -------------------------------------------------------------------------------- 1 | import { x } from 'tinyexec'; 2 | import type { JsonTreeNode } from '@/commands/file-tree'; 3 | 4 | export async function runTree(args: string): Promise { 5 | const out = await x('tree', [args, '--gitignore', '--prune', '-J']); 6 | 7 | try { 8 | return JSON.parse(out.stdout) as JsonTreeNode[]; 9 | } catch (e) { 10 | throw new Error('failed to run `tree` command', { 11 | cause: e, 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/cli/src/utils/fs.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import path from 'node:path'; 3 | 4 | export async function exists(pathLike: string): Promise { 5 | try { 6 | await fs.access(pathLike); 7 | return true; 8 | } catch { 9 | return false; 10 | } 11 | } 12 | 13 | export function isRelative(from: string, to: string): boolean { 14 | return !path.relative(from, to).startsWith(`..${path.sep}`); 15 | } 16 | -------------------------------------------------------------------------------- /packages/cli/src/utils/get-package-manager.ts: -------------------------------------------------------------------------------- 1 | import { detect, type AgentName } from 'package-manager-detector'; 2 | 3 | export type PackageManager = AgentName; 4 | 5 | export async function getPackageManager(): Promise { 6 | const result = await detect(); 7 | 8 | return result?.name ?? 'npm'; 9 | } 10 | -------------------------------------------------------------------------------- /packages/cli/src/utils/is-src.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { exists } from '@/utils/fs'; 3 | 4 | export async function isSrc(): Promise { 5 | return exists('./src'); 6 | } 7 | 8 | export function resolveAppPath(filePath: string, src: boolean): string { 9 | return src ? path.join('./src', filePath) : filePath; 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/src/utils/typescript.ts: -------------------------------------------------------------------------------- 1 | import { Project } from 'ts-morph'; 2 | 3 | export function createEmptyProject(): Project { 4 | return new Project({ 5 | compilerOptions: {}, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /packages/cli/test/fixture/layout: -------------------------------------------------------------------------------- 1 | import './global.css'; 2 | import { RootProvider } from 'fumadocs-ui/provider'; 3 | import { Inter } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | 6 | const inter = Inter({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | export default function Layout({ 11 | children, 12 | }: { 13 | children: ReactNode; 14 | }) { 15 | return ( 16 | 17 | 18 | {children} 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/cli/test/fixture/layout.out: -------------------------------------------------------------------------------- 1 | import './global.css'; 2 | import { RootProvider } from 'fumadocs-ui/provider'; 3 | import { Inter } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | import { I18nProvider } from "fumadocs-ui/i18n"; 6 | 7 | const inter = Inter({ 8 | subsets: ['latin'], 9 | }); 10 | 11 | export default async function Layout({ params, children }: { params: Promise<{ lang: string }>, children: ReactNode }) { 12 | return ( 13 | 14 | 15 | 18 | {children} 19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/cli/test/out/extended/_registry.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "popover", 4 | "description": "Popover component" 5 | }, 6 | { 7 | "name": "select" 8 | } 9 | ] -------------------------------------------------------------------------------- /packages/cli/test/out/extended/button.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "button", 3 | "files": [ 4 | { 5 | "imports": {}, 6 | "content": "export function Button(): string {\n return 'Hello';\n}\n", 7 | "path": "components:button.tsx" 8 | } 9 | ], 10 | "subComponents": [], 11 | "dependencies": {}, 12 | "devDependencies": {} 13 | } -------------------------------------------------------------------------------- /packages/cli/test/out/extended/select.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "select", 3 | "files": [ 4 | { 5 | "imports": { 6 | "../../repo/components/button": "components:button.tsx" 7 | }, 8 | "content": "import { Button } from '../../repo/components/button';\n\nexport function Select(): string {\n return Button();\n}\n", 9 | "path": "components:select.ts" 10 | } 11 | ], 12 | "subComponents": [ 13 | "button" 14 | ], 15 | "dependencies": {}, 16 | "devDependencies": {} 17 | } -------------------------------------------------------------------------------- /packages/cli/test/out/repo/_registry.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "popover", 4 | "description": "Popover component" 5 | } 6 | ] -------------------------------------------------------------------------------- /packages/cli/test/out/repo/button.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "button", 3 | "files": [ 4 | { 5 | "imports": {}, 6 | "content": "export function Button(): string {\n return 'Hello';\n}\n", 7 | "path": "components:button.tsx" 8 | } 9 | ], 10 | "subComponents": [], 11 | "dependencies": {}, 12 | "devDependencies": {} 13 | } -------------------------------------------------------------------------------- /packages/cli/test/repo-2/components/select.ts: -------------------------------------------------------------------------------- 1 | import { Button } from '../../repo/components/button'; 2 | 3 | export function Select(): string { 4 | return Button(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/cli/test/repo-2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-registry-2", 3 | "version": "0.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /packages/cli/test/repo-2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true 18 | }, 19 | "include": ["**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/cli/test/repo/components/button.tsx: -------------------------------------------------------------------------------- 1 | export function Button(): string { 2 | return 'Hello'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/cli/test/repo/components/nested/hello.tsx: -------------------------------------------------------------------------------- 1 | export function Hello(): string { 2 | return 'Hello'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/cli/test/repo/components/popover.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import * as fs from 'node:fs'; 3 | import { createContext } from 'react'; 4 | import { useExample } from '../hooks/use-example'; 5 | import { Button } from './button'; 6 | import { Hello } from './nested/hello'; 7 | 8 | const Context = createContext('test'); 9 | 10 | export function Popover(): string { 11 | console.log('This component uses button.'); 12 | const res = useExample(); 13 | console.log(res); 14 | Hello(); 15 | 16 | return Button(); 17 | } 18 | 19 | export function externalImports(): void { 20 | fs.writeFileSync('path', 'content'); 21 | console.log(Context); 22 | } 23 | -------------------------------------------------------------------------------- /packages/cli/test/repo/hooks/use-example.ts: -------------------------------------------------------------------------------- 1 | export function useExample(): string { 2 | return 'test'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/cli/test/repo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-registry-1", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "react": "^18.3.1" 6 | }, 7 | "devDependencies": { 8 | "@types/react": "^18.3.11" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/test/repo/registry.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url'; 2 | import { type Registry } from '../../src/build'; 3 | import * as path from 'node:path'; 4 | 5 | export const registry: Registry = { 6 | dir: path.dirname(fileURLToPath(import.meta.url)), 7 | rootDir: '.', 8 | namespaces: { 9 | './components': 'components', 10 | './utils': 'lib', 11 | './hooks': 'hooks', 12 | }, 13 | components: [ 14 | { 15 | name: 'button', 16 | unlisted: true, 17 | files: ['components/button.tsx'], 18 | }, 19 | { 20 | name: 'popover', 21 | description: 'Popover component', 22 | files: ['components/popover.tsx'], 23 | }, 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /packages/cli/test/repo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true 18 | }, 19 | "include": ["**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/cli/test/repo/utils/example-util.ts: -------------------------------------------------------------------------------- 1 | export function print(): void { 2 | console.log('Hello World'); 3 | } 4 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "test/repo", "test/repo-2", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/cli/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | import { sync } from './scripts/sync'; 3 | 4 | console.log('[CLI] sync generated.js'); 5 | void sync(); 6 | 7 | export default defineConfig({ 8 | entry: ['./src/index.ts', './src/build/index.ts'], 9 | format: 'esm', 10 | dts: true, 11 | target: 'node18', 12 | }); 13 | -------------------------------------------------------------------------------- /packages/cli/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | resolve: { 5 | alias: { 6 | '@/': new URL('./src/', import.meta.url).pathname, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/content-collections/README.md: -------------------------------------------------------------------------------- 1 | # Fumadocs Content Collections 2 | 3 | The Content Collections adapter for Fumadocs. 4 | -------------------------------------------------------------------------------- /packages/content-collections/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/library.js'; 2 | 3 | export default [ 4 | { 5 | ignores: ['dist', 'node_modules', '*.test.ts', 'eslint.config.mjs'], 6 | }, 7 | ...library, 8 | ]; 9 | -------------------------------------------------------------------------------- /packages/content-collections/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Source, VirtualFile } from 'fumadocs-core/source'; 2 | import type { BaseDocsData, BaseMetaData } from '@/types'; 3 | 4 | export function createMDXSource< 5 | Docs extends BaseDocsData, 6 | Meta extends BaseMetaData, 7 | >( 8 | allDocs: Docs[], 9 | allMetas: Meta[], 10 | ): Source<{ 11 | metaData: Meta; 12 | pageData: Docs; 13 | }> { 14 | return { 15 | files: [ 16 | ...allDocs.map((v) => ({ 17 | type: 'page', 18 | data: v, 19 | path: v._meta.filePath, 20 | })), 21 | ...allMetas.map((v) => ({ 22 | type: 'meta', 23 | data: v, 24 | path: v._meta.filePath, 25 | })), 26 | ], 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/content-collections/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { MetaData, PageData } from 'fumadocs-core/source'; 2 | import type { Meta } from '@content-collections/core'; 3 | 4 | export interface BaseMetaData extends MetaData { 5 | _meta: Meta; 6 | } 7 | 8 | export interface BaseDocsData extends PageData { 9 | _meta: Meta; 10 | } 11 | -------------------------------------------------------------------------------- /packages/content-collections/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/content-collections/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | dts: true, 5 | target: 'esnext', 6 | format: 'esm', 7 | entry: ['src/{index,configuration}.ts'], 8 | }); 9 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # Fumadocs Core 2 | 3 | The core library for Fumadocs. 4 | 5 | 📘 Learn More: [Documentation](https://fumadocs.vercel.app) 6 | -------------------------------------------------------------------------------- /packages/core/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import next from 'eslint-config-custom/next.js'; 2 | 3 | export default [ 4 | { 5 | ignores: ['dist/', 'node_modules/', '*.test.ts', '*.output.js'], 6 | }, 7 | ...next, 8 | { 9 | rules: { 10 | 'no-console': 'off', 11 | // handled by bundler 12 | 'import/no-cycle': 'off', 13 | }, 14 | }, 15 | ]; 16 | -------------------------------------------------------------------------------- /packages/core/src/framework/next.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { type Framework, FrameworkProvider } from '@/framework/index'; 3 | import type { ReactNode } from 'react'; 4 | import { useParams, usePathname, useRouter } from 'next/navigation'; 5 | import Link from 'next/link'; 6 | import Image from 'next/image'; 7 | 8 | export function NextProvider({ children }: { children: ReactNode }) { 9 | return ( 10 | 17 | {children} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/highlight/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | highlight, 3 | getHighlighter, 4 | type HighlightOptions, 5 | type HighlightOptionsCommon, 6 | type HighlightOptionsThemes, 7 | } from './shiki'; 8 | -------------------------------------------------------------------------------- /packages/core/src/mdx-plugins/hast-utils.ts: -------------------------------------------------------------------------------- 1 | import type { Element, Root, RootContent } from 'hast'; 2 | 3 | /** 4 | * Visit a node with filtered tag names 5 | */ 6 | export function visit( 7 | node: RootContent | Root, 8 | tagNames: string[], 9 | handler: (node: Element) => 'skip' | undefined, 10 | ): void { 11 | if (node.type === 'element' && tagNames.includes(node.tagName)) { 12 | const result = handler(node); 13 | if (result === 'skip') return; 14 | } 15 | 16 | if ('children' in node) 17 | node.children.forEach((n) => { 18 | visit(n, tagNames, handler); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/mdx-plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | default as remarkGfm, 3 | type Options as RemarkGfmOptions, 4 | } from 'remark-gfm'; 5 | export * from './rehype-code'; 6 | export * from './remark-image'; 7 | export * from './remark-structure'; 8 | export * from './remark-heading'; 9 | export * from './remark-admonition'; 10 | export * from './rehype-toc'; 11 | export * from './remark-code-tab'; 12 | export * from './remark-steps'; 13 | -------------------------------------------------------------------------------- /packages/core/src/mdx-plugins/remark-utils.ts: -------------------------------------------------------------------------------- 1 | import type { RootContent } from 'mdast'; 2 | 3 | export function flattenNode(node: RootContent): string { 4 | if ('children' in node) 5 | return node.children.map((child) => flattenNode(child)).join(''); 6 | 7 | if ('value' in node) return node.value; 8 | 9 | return ''; 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/search/orama/create-endpoint.ts: -------------------------------------------------------------------------------- 1 | import { type SearchAPI, type SearchServer } from '@/search/server'; 2 | 3 | export function createEndpoint(server: SearchServer): SearchAPI { 4 | const { search } = server; 5 | 6 | return { 7 | ...server, 8 | async staticGET() { 9 | return Response.json(await server.export()); 10 | }, 11 | async GET(request) { 12 | const url = new URL(request.url); 13 | const query = url.searchParams.get('query'); 14 | if (!query) return Response.json([]); 15 | 16 | return Response.json( 17 | await search(query, { 18 | tag: url.searchParams.get('tag') ?? undefined, 19 | locale: url.searchParams.get('locale') ?? undefined, 20 | }), 21 | ); 22 | }, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-toc'; 2 | export * from '../utils/page-tree'; 3 | export * as PageTree from './page-tree'; 4 | export * from './git-api'; 5 | export * from './types'; 6 | export * from './metadata'; 7 | -------------------------------------------------------------------------------- /packages/core/src/server/types.ts: -------------------------------------------------------------------------------- 1 | export interface SortedResult { 2 | id: string; 3 | url: string; 4 | type: 'page' | 'heading' | 'text'; 5 | content: string; 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/source/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page-tree-builder'; 2 | export * from './loader'; 3 | export * as FileSystem from './file-system'; 4 | 5 | export { 6 | loadFiles, 7 | type LoadOptions, 8 | type Transformer, 9 | type VirtualFile, 10 | } from './load-files'; 11 | export type * from './types'; 12 | export { 13 | type FileInfo, 14 | type FolderInfo, 15 | parseFilePath, 16 | parseFolderPath, 17 | } from './path'; 18 | -------------------------------------------------------------------------------- /packages/core/src/utils/merge-refs.ts: -------------------------------------------------------------------------------- 1 | import type * as React from 'react'; 2 | 3 | export function mergeRefs(...refs: React.Ref[]): React.RefCallback { 4 | return (value) => { 5 | refs.forEach((ref) => { 6 | if (typeof ref === 'function') { 7 | ref(value); 8 | } else if (ref !== null) { 9 | ref.current = value; 10 | } 11 | }); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/utils/remove-undefined.ts: -------------------------------------------------------------------------------- 1 | export function removeUndefined(value: T, deep = false): T { 2 | const obj = value as Record; 3 | 4 | for (const key of Object.keys(obj)) { 5 | if (obj[key] === undefined) delete obj[key]; 6 | 7 | if (deep && typeof obj[key] === 'object' && obj[key] !== null) { 8 | removeUndefined(obj[key], deep); 9 | } else if (deep && Array.isArray(obj[key])) { 10 | obj[key].forEach((v) => removeUndefined(v, deep)); 11 | } 12 | } 13 | 14 | return value; 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/utils/use-debounce.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | 3 | export function useDebounce(value: T, delayMs = 1000): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | const timer = useRef<{ value: T; handler: number } | undefined>(undefined); 6 | 7 | if (delayMs === 0) return value; 8 | 9 | if (value !== debouncedValue && timer.current?.value !== value) { 10 | if (timer.current) clearTimeout(timer.current.handler); 11 | 12 | const handler = window.setTimeout(() => { 13 | setDebouncedValue(value); 14 | }, delayMs); 15 | timer.current = { value, handler }; 16 | } 17 | 18 | return debouncedValue; 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/utils/use-effect-event.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useCallback, useRef } from 'react'; 3 | 4 | /** 5 | * Don't use this, could be deleted anytime. 6 | * 7 | * @internal 8 | */ 9 | export function useEffectEvent unknown>( 10 | callback: F, 11 | ): F { 12 | const ref = useRef(callback); 13 | ref.current = callback; 14 | 15 | return useCallback(((...params) => ref.current(...params)) as F, []); 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/utils/use-media-query.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useMediaQuery(query: string, disabled = false): boolean | null { 4 | const [isMatch, setMatch] = useState(null); 5 | 6 | useEffect(() => { 7 | if (disabled) return; 8 | const mediaQueryList = window.matchMedia(query); 9 | 10 | const handleChange = () => { 11 | setMatch(mediaQueryList.matches); 12 | }; 13 | handleChange(); 14 | mediaQueryList.addEventListener('change', handleChange); 15 | return () => { 16 | mediaQueryList.removeEventListener('change', handleChange); 17 | }; 18 | }, [disabled, query]); 19 | 20 | return isMatch; 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/utils/use-on-change.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | function isDifferent(a: unknown, b: unknown): boolean { 4 | if (Array.isArray(a) && Array.isArray(b)) { 5 | return b.length !== a.length || a.some((v, i) => isDifferent(v, b[i])); 6 | } 7 | 8 | return a !== b; 9 | } 10 | 11 | /** 12 | * @param value - state to watch 13 | * @param onChange - when the state changed 14 | * @param isUpdated - a function that determines if the state is updated 15 | */ 16 | export function useOnChange( 17 | value: T, 18 | onChange: (current: T, previous: T) => void, 19 | isUpdated: (prev: T, current: T) => boolean = isDifferent, 20 | ): void { 21 | const [prev, setPrev] = useState(value); 22 | 23 | if (isUpdated(prev, value)) { 24 | onChange(value, prev); 25 | setPrev(value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/rehype-toc.md: -------------------------------------------------------------------------------- 1 | # Heading 1 2 | 3 | Some text here 4 | 5 | ## Heading 2 6 | 7 | Some text here 8 | 9 | ### Heading 3 10 | 11 | Some text here 12 | 13 | ### Custom heading id [#hello-world] 14 | 15 | Some text here 16 | 17 | ### math $$C_L$$ 18 | 19 | Some text here 20 | 21 | ### Custom heading id hello-world] 22 | 23 | Some text here 24 | 25 | ### `code` here 26 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-admonition.md: -------------------------------------------------------------------------------- 1 | :::tip 2 | 3 | Hello World 4 | 5 | ::: 6 | 7 | :::warn[This is Title] 8 | 9 | Warning **this** 10 | 11 | ::: 12 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-admonition.output.mdx: -------------------------------------------------------------------------------- 1 | 2 | Hello World 3 | 4 | 5 | 6 | Warning **this** 7 | 8 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-heading.md: -------------------------------------------------------------------------------- 1 | # Heading 1 2 | 3 | Some text here 4 | 5 | ## Heading 2 6 | 7 | Some text here 8 | 9 | ### Heading 3 10 | 11 | Some text here 12 | 13 | ### Custom heading id [#hello-world] 14 | 15 | Some text here 16 | 17 | ### Custom heading id [#hello-2] 18 | 19 | Some text here 20 | 21 | ### Custom heading id hello-world] 22 | 23 | Some text here 24 | 25 | ### `code` here 26 | 27 | hi 28 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-heading.output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "depth": 1, 4 | "title": "Heading 1", 5 | "url": "#heading-1", 6 | }, 7 | { 8 | "depth": 2, 9 | "title": "Heading 2", 10 | "url": "#heading-2", 11 | }, 12 | { 13 | "depth": 3, 14 | "title": "Heading 3", 15 | "url": "#heading-3", 16 | }, 17 | { 18 | "depth": 3, 19 | "title": "Custom heading id", 20 | "url": "#hello-world", 21 | }, 22 | { 23 | "depth": 3, 24 | "title": "Custom heading id", 25 | "url": "#hello-2", 26 | }, 27 | { 28 | "depth": 3, 29 | "title": "Custom heading id hello-world]", 30 | "url": "#custom-heading-id-hello-world", 31 | }, 32 | { 33 | "depth": 3, 34 | "title": "code here", 35 | "url": "#code-here", 36 | }, 37 | ] -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-image-public-dir.md: -------------------------------------------------------------------------------- 1 | ![External](/237/200/300) 2 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-image-public-dir.output.mdx: -------------------------------------------------------------------------------- 1 | External 2 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-image-without-import.output.mdx: -------------------------------------------------------------------------------- 1 | Test 2 | 3 | External 4 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-image.md: -------------------------------------------------------------------------------- 1 | ![Test](./test.png) 2 | 3 | ![External](https://picsum.photos/id/237/200/300) 4 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-steps.md: -------------------------------------------------------------------------------- 1 | # Hello 2 | 3 | ## 1. First 4 | 5 | content 6 | 7 | ### Little Tip 8 | 9 | content 10 | 11 | ## 2. Second 12 | 13 | content 14 | 15 | ## 1. Third 16 | 17 | content 18 | 19 | ## Ended 20 | 21 | content 22 | 23 | # 1. Big: First 24 | 25 | content 26 | 27 | # 2. Big: Second 28 | 29 | content 30 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-steps.output.md: -------------------------------------------------------------------------------- 1 | # Hello 2 | 3 |
4 |
5 | ## First 6 | 7 | content 8 | 9 | ### Little Tip 10 | 11 | content 12 |
13 | 14 |
15 | ## Second 16 | 17 | content 18 |
19 | 20 |
21 | ## Third 22 | 23 | content 24 |
25 |
26 | 27 | ## Ended 28 | 29 | content 30 | 31 |
32 |
33 | # Big: First 34 | 35 | content 36 |
37 | 38 |
39 | # Big: Second 40 | 41 | content 42 |
43 |
44 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/remark-structure.md: -------------------------------------------------------------------------------- 1 | # Heading 1 2 | 3 | Some text here 4 | 5 | ## Heading 2 6 | 7 | Some text here 8 | 9 | ### Heading 3 10 | 11 | | Name | Description | 12 | | ----------- | ----------- | 13 | | Hello World | Goodbye | 14 | 15 | ```ts 16 | console.log('Not indexed'); 17 | ``` 18 | -------------------------------------------------------------------------------- /packages/core/test/fixtures/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/packages/core/test/fixtures/test.png -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | resolve: { 5 | alias: { 6 | '@/': new URL('./src/', import.meta.url).pathname, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/create-app/.gitignore: -------------------------------------------------------------------------------- 1 | versions.js -------------------------------------------------------------------------------- /packages/create-app/README.md: -------------------------------------------------------------------------------- 1 | # Create Fumadocs App 2 | 3 | A CLI tool to create new Next.js documentation sites with Fumadocs. 4 | 5 | ```bash 6 | npx create-fumadocs-app 7 | #or 8 | pnpm create fumadocs-app 9 | #or 10 | yarn create fumadocs-app 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/create-app/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/next.js'; 2 | 3 | export default [ 4 | { 5 | ignores: ['dist/', 'scripts/', 'node_modules/', 'template/'], 6 | }, 7 | ...library, 8 | { 9 | rules: { 10 | 'import/no-relative-packages': 'off', 11 | }, 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/create-app/scripts/update-git-repo.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import { create } from '../dist/create-app.js'; 4 | 5 | const repo = process.argv[2] ?? './fumadocs-ui-template'; 6 | 7 | fs.readdirSync(repo).forEach((file) => { 8 | if (file !== '.git') { 9 | fs.rmSync(path.join(repo, file), { 10 | recursive: true, 11 | force: true, 12 | }); 13 | } 14 | }); 15 | 16 | await create({ 17 | outputDir: repo, 18 | template: '+next+fuma-docs-mdx', 19 | tailwindcss: false, 20 | installDeps: false, 21 | packageManager: 'npm', 22 | }); 23 | -------------------------------------------------------------------------------- /packages/create-app/src/constants.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url'; 2 | 3 | export const sourceDir = fileURLToPath(new URL(`../`, import.meta.url).href); 4 | export const cwd = process.cwd(); 5 | -------------------------------------------------------------------------------- /packages/create-app/src/versions.d.ts: -------------------------------------------------------------------------------- 1 | export const versions: { 2 | 'fumadocs-core': string; 3 | 'fumadocs-ui': string; 4 | 'fumadocs-mdx': string; 5 | '@fumadocs/mdx-remote': string; 6 | '@fumadocs/content-collections': string; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+content-collections/content-collections.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, defineConfig } from '@content-collections/core'; 2 | import { 3 | createMetaSchema, 4 | createDocSchema, 5 | transformMDX, 6 | } from '@fumadocs/content-collections/configuration'; 7 | 8 | const docs = defineCollection({ 9 | name: 'docs', 10 | directory: 'content/docs', 11 | include: '**/*.mdx', 12 | schema: createDocSchema, 13 | transform: transformMDX, 14 | }); 15 | 16 | const metas = defineCollection({ 17 | name: 'meta', 18 | directory: 'content/docs', 19 | include: '**/meta.json', 20 | parser: 'json', 21 | schema: createMetaSchema, 22 | }); 23 | 24 | export default defineConfig({ 25 | collections: [docs, metas], 26 | }); 27 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+content-collections/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { allDocs, allMetas } from 'content-collections'; 2 | import { loader } from 'fumadocs-core/source'; 3 | import { createMDXSource } from '@fumadocs/content-collections'; 4 | 5 | export const source = loader({ 6 | baseUrl: '/docs', 7 | source: createMDXSource(allDocs, allMetas), 8 | }); 9 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+content-collections/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { withContentCollections } from '@content-collections/next'; 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const config = { 5 | reactStrictMode: true, 6 | }; 7 | 8 | export default withContentCollections(config); 9 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+eslint/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+fuma-docs-mdx/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { docs } from '@/.source'; 2 | import { loader } from 'fumadocs-core/source'; 3 | 4 | // See https://fumadocs.vercel.app/docs/headless/source-api for more info 5 | export const source = loader({ 6 | // it assigns a URL to your pages 7 | baseUrl: '/docs', 8 | source: docs.toFumadocsSource(), 9 | }); 10 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+fuma-docs-mdx/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { createMDX } from 'fumadocs-mdx/next'; 2 | 3 | const withMDX = createMDX(); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const config = { 7 | reactStrictMode: true, 8 | }; 9 | 10 | export default withMDX(config); 11 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+fuma-docs-mdx/source.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, 3 | defineDocs, 4 | frontmatterSchema, 5 | metaSchema, 6 | } from 'fumadocs-mdx/config'; 7 | 8 | // You can customise Zod schemas for frontmatter and `meta.json` here 9 | // see https://fumadocs.vercel.app/docs/mdx/collections#define-docs 10 | export const docs = defineDocs({ 11 | docs: { 12 | schema: frontmatterSchema, 13 | }, 14 | meta: { 15 | schema: metaSchema, 16 | }, 17 | }); 18 | 19 | export default defineConfig({ 20 | mdxOptions: { 21 | // MDX options 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+tailwindcss/app/(home)/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function HomePage() { 4 | return ( 5 |
6 |

Hello World

7 |

8 | You can open{' '} 9 | 13 | /docs 14 | {' '} 15 | and see the documentation. 16 |

17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+tailwindcss/app/global.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+tailwindcss/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './global.css'; 2 | import { RootProvider } from 'fumadocs-ui/provider'; 3 | import { Inter } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | 6 | const inter = Inter({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | export default function Layout({ children }: { children: ReactNode }) { 11 | return ( 12 | 13 | 14 | {children} 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/create-app/template/+next+tailwindcss/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/create-app/template/+next/app/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { HomeLayout } from 'fumadocs-ui/layouts/home'; 3 | import { baseOptions } from '@/app/layout.config'; 4 | 5 | export default function Layout({ children }: { children: ReactNode }) { 6 | return {children}; 7 | } 8 | -------------------------------------------------------------------------------- /packages/create-app/template/+next/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createFromSource } from 'fumadocs-core/search/server'; 3 | 4 | export const { GET } = createFromSource(source); 5 | -------------------------------------------------------------------------------- /packages/create-app/template/+next/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DocsLayout } from 'fumadocs-ui/layouts/docs'; 2 | import type { ReactNode } from 'react'; 3 | import { baseOptions } from '@/app/layout.config'; 4 | import { source } from '@/lib/source'; 5 | 6 | export default function Layout({ children }: { children: ReactNode }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/create-app/template/+next/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; 2 | 3 | /** 4 | * Shared layout configurations 5 | * 6 | * you can customise layouts individually from: 7 | * Home Layout: app/(home)/layout.tsx 8 | * Docs Layout: app/docs/layout.tsx 9 | */ 10 | export const baseOptions: BaseLayoutProps = { 11 | nav: { 12 | title: ( 13 | <> 14 | 20 | 21 | 22 | My App 23 | 24 | ), 25 | }, 26 | // see https://fumadocs.dev/docs/ui/navigation/links 27 | links: [], 28 | }; 29 | -------------------------------------------------------------------------------- /packages/create-app/template/+next/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { RootProvider } from 'fumadocs-ui/provider'; 2 | import 'fumadocs-ui/style.css'; 3 | import { Inter } from 'next/font/google'; 4 | import type { ReactNode } from 'react'; 5 | 6 | const inter = Inter({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | export default function Layout({ children }: { children: ReactNode }) { 11 | return ( 12 | 13 | 20 | {children} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/create-app/template/+next/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | description: Your first document 4 | --- 5 | 6 | Welcome to the docs! You can start writing documents in `/content/docs`. 7 | 8 | ## What is Next? 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/create-app/template/+next/content/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Components 3 | description: Components 4 | --- 5 | 6 | ## Code Block 7 | 8 | ```js 9 | console.log('Hello World'); 10 | ``` 11 | 12 | ## Cards 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/create-app/template/+next/example.gitignore: -------------------------------------------------------------------------------- 1 | # deps 2 | /node_modules 3 | 4 | # generated content 5 | .contentlayer 6 | .content-collections 7 | .source 8 | 9 | # test & build 10 | /coverage 11 | /.next/ 12 | /out/ 13 | /build 14 | *.tsbuildinfo 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | /.pnp 20 | .pnp.js 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # others 26 | .env*.local 27 | .vercel 28 | next-env.d.ts -------------------------------------------------------------------------------- /packages/create-app/template/+next/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultMdxComponents from 'fumadocs-ui/mdx'; 2 | import type { MDXComponents } from 'mdx/types'; 3 | 4 | // use this function to get MDX components, you will need it for rendering MDX 5 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 6 | return { 7 | ...defaultMdxComponents, 8 | ...components, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/README.md: -------------------------------------------------------------------------------- 1 | This is a React Router application generated with 2 | [Create Fumadocs](https://github.com/fuma-nama/fumadocs). 3 | 4 | Run development server: 5 | 6 | ```bash 7 | npm run dev 8 | # or 9 | pnpm dev 10 | # or 11 | yarn dev 12 | ``` 13 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/app/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/app/docs/search.ts: -------------------------------------------------------------------------------- 1 | import type { Route } from './+types/search'; 2 | import { createSearchAPI } from 'fumadocs-core/search/server'; 3 | import { source } from '@/source'; 4 | import { structure } from 'fumadocs-core/mdx-plugins'; 5 | 6 | const server = createSearchAPI('advanced', { 7 | indexes: source.getPages().map((page) => ({ 8 | id: page.url, 9 | url: page.url, 10 | title: page.data.title ?? '', 11 | description: page.data.description, 12 | structuredData: structure(page.data.content), 13 | })), 14 | }); 15 | 16 | export async function loader({ request }: Route.LoaderArgs) { 17 | return server.GET(request); 18 | } 19 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { type RouteConfig, index, route } from '@react-router/dev/routes'; 2 | 3 | export default [ 4 | index('routes/home.tsx'), 5 | route('docs/*', 'docs/page.tsx'), 6 | route('api/search', 'docs/search.ts'), 7 | ] satisfies RouteConfig; 8 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/content/docs/test.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fumadocs 3 | description: You can just be minimal. 4 | --- 5 | 6 | ## Overview 7 | 8 | Fumadocs is a docs framework. 9 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/example.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | 4 | # React Router 5 | /.react-router/ 6 | /build/ 7 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/packages/create-app/template/react-router/public/favicon.ico -------------------------------------------------------------------------------- /packages/create-app/template/react-router/react-router.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@react-router/dev/config'; 2 | import { source } from './app/source'; 3 | 4 | export default { 5 | ssr: true, 6 | async prerender({ getStaticPaths }) { 7 | return [...getStaticPaths(), ...source.getPages().map((page) => page.url)]; 8 | }, 9 | } satisfies Config; 10 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*", 4 | "**/.server/**/*", 5 | "**/.client/**/*", 6 | ".react-router/types/**/*" 7 | ], 8 | "compilerOptions": { 9 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 10 | "types": ["node", "vite/client"], 11 | "target": "esnext", 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "jsx": "react-jsx", 15 | "rootDirs": [".", "./.react-router/types"], 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./app/*"] 19 | }, 20 | "esModuleInterop": true, 21 | "verbatimModuleSyntax": true, 22 | "noEmit": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/create-app/template/react-router/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { reactRouter } from '@react-router/dev/vite'; 2 | import tailwindcss from '@tailwindcss/vite'; 3 | import { defineConfig } from 'vite'; 4 | import tsconfigPaths from 'vite-tsconfig-paths'; 5 | 6 | export default defineConfig({ 7 | build: { 8 | rollupOptions: { 9 | external: ['shiki'], 10 | }, 11 | }, 12 | plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], 13 | }); 14 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/README.md: -------------------------------------------------------------------------------- 1 | This is a Tanstack Start application generated with 2 | [Create Fumadocs](https://github.com/fuma-nama/fumadocs). 3 | 4 | Run development server: 5 | 6 | ```bash 7 | npm run dev 8 | # or 9 | pnpm dev 10 | # or 11 | yarn dev 12 | ``` 13 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/app/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createStartAPIHandler, 3 | defaultAPIFileRouteHandler, 4 | } from '@tanstack/react-start/api'; 5 | 6 | export default createStartAPIHandler(defaultAPIFileRouteHandler); 7 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/app/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/app/client.tsx: -------------------------------------------------------------------------------- 1 | import { hydrateRoot } from 'react-dom/client'; 2 | import { StartClient } from '@tanstack/react-start'; 3 | import { createRouter } from './router'; 4 | 5 | const router = createRouter(); 6 | 7 | hydrateRoot(document, ); 8 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/app/router.tsx: -------------------------------------------------------------------------------- 1 | import { createRouter as createTanStackRouter } from '@tanstack/react-router'; 2 | import { routeTree } from './routeTree.gen'; 3 | 4 | export function createRouter() { 5 | return createTanStackRouter({ 6 | routeTree, 7 | scrollRestoration: true, 8 | }); 9 | } 10 | 11 | declare module '@tanstack/react-router' { 12 | interface Register { 13 | router: ReturnType; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/app/routes/api/search.ts: -------------------------------------------------------------------------------- 1 | import { createAPIFileRoute } from '@tanstack/react-start/api'; 2 | import { createSearchAPI } from 'fumadocs-core/search/server'; 3 | import { source } from '@/lib/source'; 4 | import { structure } from 'fumadocs-core/mdx-plugins'; 5 | 6 | const server = createSearchAPI('advanced', { 7 | indexes: source.getPages().map((page) => ({ 8 | id: page.url, 9 | url: page.url, 10 | title: page.data.title ?? '', 11 | description: page.data.description, 12 | structuredData: structure(page.data.content), 13 | })), 14 | }); 15 | 16 | export const APIRoute = createAPIFileRoute('/api/search')({ 17 | GET: ({ request }) => { 18 | return server.GET(request); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute, Link } from '@tanstack/react-router'; 2 | import { HomeLayout } from 'fumadocs-ui/layouts/home'; 3 | 4 | export const Route = createFileRoute('/')({ 5 | component: Home, 6 | }); 7 | 8 | function Home() { 9 | return ( 10 | 16 |

Fumadocs on Tanstack Start.

17 | 21 | Open Docs 22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/app/ssr.tsx: -------------------------------------------------------------------------------- 1 | // app/ssr.tsx 2 | import { 3 | createStartHandler, 4 | defaultStreamHandler, 5 | } from '@tanstack/react-start/server'; 6 | import { getRouterManifest } from '@tanstack/react-start/router-manifest'; 7 | 8 | import { createRouter } from './router'; 9 | 10 | export default createStartHandler({ 11 | createRouter, 12 | getRouterManifest, 13 | })(defaultStreamHandler); 14 | -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/example.gitignore: -------------------------------------------------------------------------------- 1 | routeTree.gen.ts 2 | node_modules 3 | .output 4 | .vinxi -------------------------------------------------------------------------------- /packages/create-app/template/tanstack-start/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "moduleResolution": "Bundler", 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "types": ["node", "vite/client"], 7 | "module": "ESNext", 8 | "target": "ESNext", 9 | "skipLibCheck": true, 10 | "strictNullChecks": true, 11 | "paths": { 12 | "@/*": ["./*"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/create-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "paths": { 6 | "@/*": ["./src/*"] 7 | } 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "template", 12 | "dist", 13 | "scripts", 14 | "eslint.config.mjs" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/doc-gen/README.md: -------------------------------------------------------------------------------- 1 | # Fumadocs Doc Gen 2 | 3 | Remark plugins & Docs Generator utilities. 4 | -------------------------------------------------------------------------------- /packages/doc-gen/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/library.js'; 2 | 3 | export default [ 4 | ...library, 5 | { 6 | ignores: ['dist/', 'node_modules/', '*.test.ts', '**/test/fixtures/'], 7 | }, 8 | ]; 9 | -------------------------------------------------------------------------------- /packages/doc-gen/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './remark-docgen'; 2 | export * from './utils'; 3 | export * from './file-generator'; 4 | export * from './remark-install'; 5 | export * from './remark-show'; 6 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/file-gen.md: -------------------------------------------------------------------------------- 1 | ## Hello World 2 | 3 | ```json doc-gen:file 4 | { 5 | "file": "./fixtures/sample.txt" 6 | } 7 | ``` 8 | 9 | ## Code Block 10 | 11 | ```json doc-gen:file 12 | { 13 | "file": "./fixtures/sample.ts", 14 | "codeblock": true 15 | } 16 | ``` 17 | 18 | ## Code Block with config 19 | 20 | ```json doc-gen:file 21 | { 22 | "file": "./fixtures/sample.txt", 23 | "codeblock": { 24 | "lang": "md", 25 | "meta": "title=\"Hello World\"" 26 | } 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/file-gen.output.md: -------------------------------------------------------------------------------- 1 | ## Hello World 2 | 3 | Hello World 4 | 5 | This is some text 6 | 7 | ## Code Block 8 | 9 | ```ts 10 | export interface Test1 { 11 | name: string; 12 | description: string; 13 | isGood: boolean; 14 | } 15 | ``` 16 | 17 | ## Code Block with config 18 | 19 | ```md title="Hello World" 20 | Hello World 21 | 22 | This is some text 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/file-gen.relative.md: -------------------------------------------------------------------------------- 1 | ## Hello World 2 | 3 | ```json doc-gen:file 4 | { 5 | "file": "./sample.txt", 6 | "codeblock": true 7 | } 8 | ``` 9 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/file-gen.relative.output.md: -------------------------------------------------------------------------------- 1 | ## Hello World 2 | 3 | ```txt 4 | Hello World 5 | 6 | This is some text 7 | ``` 8 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/remark-install-persist.md: -------------------------------------------------------------------------------- 1 | ```package-install 2 | npm i next -D 3 | ``` 4 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/remark-install.md: -------------------------------------------------------------------------------- 1 | ```package-install 2 | next 3 | ``` 4 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/remark-show.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Hello World 4 | 5 | ```ts 6 | console.log("Goodbye") 7 | ``` 8 | 9 | 10 | 11 | test()}> 12 | 13 | ## Test 14 | 15 | ```ts 16 | console.log("Test") 17 | ``` 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/remark-show.output.jsx: -------------------------------------------------------------------------------- 1 | /*@jsxRuntime automatic*/ 2 | /*@jsxImportSource react*/ 3 | function _createMdxContent(props) { 4 | const _components = { 5 | code: "code", 6 | h2: "h2", 7 | pre: "pre", 8 | ...props.components 9 | }; 10 | return <><><_components.h2>{"Hello World"}<_components.pre><_components.code className="language-ts">{"console.log(\"Goodbye\")\n"}{"\n"}<>; 11 | } 12 | export default function MDXContent(props = {}) { 13 | const {wrapper: MDXLayout} = props.components || ({}); 14 | return MDXLayout ? <_createMdxContent {...props} /> : _createMdxContent(props); 15 | } 16 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/sample.ts: -------------------------------------------------------------------------------- 1 | export interface Test1 { 2 | name: string; 3 | description: string; 4 | isGood: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/sample.txt: -------------------------------------------------------------------------------- 1 | Hello World 2 | 3 | This is some text -------------------------------------------------------------------------------- /packages/doc-gen/test/fixtures/ts2js.md: -------------------------------------------------------------------------------- 1 | ```tsx ts2js 2 | import { ReactNode } from 'react'; 3 | 4 | export default function Layout({ children }: { children: ReactNode }) { 5 | const v: string = 'hello world' as any; 6 | 7 | return ( 8 |
9 | {children} {v} 10 |
11 | ); 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /packages/doc-gen/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/doc-gen/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | external: [], 5 | dts: true, 6 | target: 'es6', 7 | format: 'esm', 8 | entry: ['src/index.ts', 'src/remark-ts2js.ts'], 9 | }); 10 | -------------------------------------------------------------------------------- /packages/doc-gen/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | resolve: { 5 | alias: { 6 | '@/': new URL('./src/', import.meta.url).pathname, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /packages/mdx-remote/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/library.js'; 2 | 3 | export default [ 4 | { 5 | ignores: ['dist/', 'node_modules/', 'test/**/*'], 6 | }, 7 | ...library, 8 | { 9 | rules: { 10 | 'import/no-named-as-default': 'off', 11 | 'import/no-named-as-default-member': 'off', 12 | }, 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /packages/mdx-remote/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../render'; 2 | -------------------------------------------------------------------------------- /packages/mdx-remote/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Pluggable } from 'unified'; 2 | import matter from 'gray-matter'; 3 | 4 | export type ResolvePlugins = Pluggable[] | ((v: Pluggable[]) => Pluggable[]); 5 | 6 | export function pluginOption( 7 | def: (v: Pluggable[]) => (Pluggable | false | null)[], 8 | options: ResolvePlugins = [], 9 | ): Pluggable[] { 10 | const list = def(Array.isArray(options) ? options : []).filter( 11 | Boolean, 12 | ) as Pluggable[]; 13 | 14 | if (typeof options === 'function') { 15 | return options(list); 16 | } 17 | 18 | return list; 19 | } 20 | 21 | /** 22 | * Parse frontmatter, currently powered by `gray-matter` 23 | */ 24 | export function parseFrontmatter(content: string) { 25 | const out = matter(content); 26 | 27 | return { 28 | frontmatter: out.data, 29 | content: out.content, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /packages/mdx-remote/test/fixtures/file.mdx: -------------------------------------------------------------------------------- 1 | ## You **found** `me`! 2 | -------------------------------------------------------------------------------- /packages/mdx-remote/test/fixtures/file.mdx.json: -------------------------------------------------------------------------------- 1 | { 2 | "frontmatter": {}, 3 | "toc": [ 4 | { 5 | "depth": 2, 6 | "title": "You found me!", 7 | "url": "#you-found-me", 8 | }, 9 | ], 10 | } -------------------------------------------------------------------------------- /packages/mdx-remote/test/fixtures/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | description: Something 4 | --- 5 | 6 | Hey! 7 | 8 | ```tsx 9 | console.log('Hello World'); 10 | ``` 11 | 12 | | Name | Description | 13 | | ---- | ----------- | 14 | | Fuma | Love anime | 15 | -------------------------------------------------------------------------------- /packages/mdx-remote/test/fixtures/index.mdx.json: -------------------------------------------------------------------------------- 1 | { 2 | "frontmatter": { 3 | "description": "Something", 4 | "title": "Hello World", 5 | }, 6 | "toc": [], 7 | } -------------------------------------------------------------------------------- /packages/mdx-remote/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/mdx-remote/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | external: ['fumadocs-core', 'next', 'react'], 5 | dts: true, 6 | target: 'es2021', 7 | entry: ['./src/index.ts', './src/client/index.ts'], 8 | format: 'esm', 9 | }); 10 | -------------------------------------------------------------------------------- /packages/mdx-remote/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | resolve: { 5 | alias: { 6 | '@/': new URL('./src/', import.meta.url).pathname, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/mdx/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { postInstall } from './dist/next/index.js'; 4 | 5 | void postInstall(process.argv[2], process.argv[3]); 6 | -------------------------------------------------------------------------------- /packages/mdx/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/library.js'; 2 | 3 | export default [ 4 | { 5 | ignores: [ 6 | 'dist/', 7 | 'node_modules/', 8 | 'test/**/*.output.js', 9 | 'bin.js', 10 | 'loader-mdx.cjs', 11 | ], 12 | }, 13 | ...library, 14 | { 15 | rules: { 16 | 'import/no-named-as-default': 'off', 17 | 'import/no-named-as-default-member': 'off', 18 | '@typescript-eslint/no-explicit-any': 'off', 19 | }, 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /packages/mdx/loader-mdx.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function loader(code) { 2 | const callback = this.async(); 3 | 4 | import('./dist/loader-mdx.js').then((mod) => 5 | mod.default.call(this, code, callback), 6 | ); 7 | }; 8 | -------------------------------------------------------------------------------- /packages/mdx/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './define'; 3 | export * from '../utils/mdx-options'; 4 | export { frontmatterSchema, metaSchema } from '../utils/schema'; 5 | export * from '../mdx-plugins/remark-include'; 6 | -------------------------------------------------------------------------------- /packages/mdx/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './runtime'; 2 | -------------------------------------------------------------------------------- /packages/mdx/src/map/file-cache.ts: -------------------------------------------------------------------------------- 1 | import { LRUCache } from 'lru-cache'; 2 | 3 | const map = new LRUCache({ 4 | max: 200, 5 | }); 6 | 7 | export const fileCache = { 8 | read(namespace: string, path: string) { 9 | return map.get(`${namespace}.${path}`) as Data; 10 | }, 11 | write(namespace: string, path: string, data: unknown) { 12 | map.set(`${namespace}.${path}`, data as object); 13 | }, 14 | removeCache(path: string) { 15 | for (const key of map.keys()) { 16 | const keyPath = key.slice(key.indexOf('.') + 1); 17 | 18 | if (keyPath === path) map.delete(key); 19 | } 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/mdx/src/map/watcher.ts: -------------------------------------------------------------------------------- 1 | import { FSWatcher } from 'chokidar'; 2 | import { type LoadedConfig } from '@/utils/config'; 3 | 4 | export function watcher( 5 | configPath: string, 6 | config: LoadedConfig, 7 | ignored: string[], 8 | ): FSWatcher { 9 | const watcher = new FSWatcher({ 10 | ignoreInitial: true, 11 | persistent: true, 12 | ignored, 13 | }); 14 | 15 | watcher.add(configPath); 16 | 17 | for (const collection of config.collections.values()) { 18 | if (collection.type === 'docs') { 19 | watcher.add(collection.docs.dir); 20 | watcher.add(collection.meta.dir); 21 | } else { 22 | watcher.add(collection.dir); 23 | } 24 | } 25 | 26 | return watcher; 27 | } 28 | -------------------------------------------------------------------------------- /packages/mdx/src/next/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create'; 2 | export * from '../postinstall'; 3 | -------------------------------------------------------------------------------- /packages/mdx/src/postinstall.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path'; 2 | import * as fs from 'node:fs/promises'; 3 | import { findConfigFile, getConfigHash, loadConfig } from '@/utils/config'; 4 | import { generateJS } from '@/map/generate'; 5 | 6 | export async function postInstall( 7 | configPath = findConfigFile(), 8 | outDir = '.source', 9 | ): Promise { 10 | const jsOut = path.resolve(outDir, 'index.ts'); 11 | const hash = await getConfigHash(configPath); 12 | const config = await loadConfig(configPath, outDir, hash, true); 13 | 14 | // clean past results 15 | await fs.rm(path.dirname(jsOut), { recursive: true }); 16 | 17 | await fs.mkdir(path.dirname(jsOut), { recursive: true }); 18 | await fs.writeFile(jsOut, await generateJS(configPath, config, jsOut, hash)); 19 | console.log('[MDX] types generated'); 20 | } 21 | -------------------------------------------------------------------------------- /packages/mdx/src/utils/get-type-from-path.ts: -------------------------------------------------------------------------------- 1 | import { extname } from 'node:path'; 2 | 3 | const docTypes = ['.mdx', '.md']; 4 | const metaTypes = ['.json', '.yaml']; 5 | 6 | export function getTypeFromPath(path: string): 'doc' | 'meta' | undefined { 7 | const ext = extname(path); 8 | 9 | if (docTypes.includes(ext)) return 'doc'; 10 | if (metaTypes.includes(ext)) return 'meta'; 11 | } 12 | -------------------------------------------------------------------------------- /packages/mdx/src/utils/git-timestamp.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { x } from 'tinyexec'; 3 | 4 | const cache = new Map(); 5 | 6 | /** 7 | * Requires `git` to be installed 8 | * 9 | * if you are using Vercel, please set `VERCEL_DEEP_CLONE` environment variable to `true` 10 | */ 11 | export async function getGitTimestamp(file: string): Promise { 12 | const cached = cache.get(file); 13 | if (cached) return cached; 14 | 15 | try { 16 | const out = await x( 17 | 'git', 18 | ['log', '-1', '--pretty="%ai"', path.relative(process.cwd(), file)], 19 | { 20 | throwOnError: true, 21 | }, 22 | ); 23 | 24 | const time = new Date(out.stdout); 25 | cache.set(file, time); 26 | return time; 27 | } catch { 28 | return; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/mdx/test/fixtures/folder/test.mdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuma-nama/fumadocs/89adec9e8692f4dba171bd3ec64f8dc0dafee61b/packages/mdx/test/fixtures/folder/test.mdx -------------------------------------------------------------------------------- /packages/mdx/test/fixtures/index-async.output.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck -- skip type checking 2 | import { _runtimeAsync, buildConfig } from "fumadocs-mdx/runtime/async" 3 | const [err, _sourceConfig] = buildConfig(_source) 4 | if (!_sourceConfig) throw new Error(err) 5 | import { _runtime } from "fumadocs-mdx" 6 | import * as _source from "./config" 7 | export const docs = _runtimeAsync.doc([{"info":{"path":"index.mdx","absolutePath":"$cwd/packages/mdx/test/fixtures/index.mdx"},"data":{},"content":"# Hello World\n"}, {"info":{"path":"folder/test.mdx","absolutePath":"$cwd/packages/mdx/test/fixtures/folder/test.mdx"},"data":{},"content":""}], "docs", _sourceConfig) -------------------------------------------------------------------------------- /packages/mdx/test/fixtures/index-sync.output.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck -- skip type checking 2 | import * as docs_1 from "./folder/test.mdx?collection=docs&hash=hash" 3 | import * as docs_0 from "./index.mdx?collection=docs&hash=hash" 4 | import { _runtime } from "fumadocs-mdx" 5 | import * as _source from "./config" 6 | export const docs = _runtime.doc([{ info: {"path":"index.mdx","absolutePath":"$cwd/packages/mdx/test/fixtures/index.mdx"}, data: docs_0 }, { info: {"path":"folder/test.mdx","absolutePath":"$cwd/packages/mdx/test/fixtures/folder/test.mdx"}, data: docs_1 }]); -------------------------------------------------------------------------------- /packages/mdx/test/fixtures/index.mdx: -------------------------------------------------------------------------------- 1 | # Hello World 2 | -------------------------------------------------------------------------------- /packages/mdx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/mdx/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | entry: [ 5 | './src/{index,loader-mdx}.ts', 6 | './src/{config,next}/index.ts', 7 | './src/runtime/async.ts', 8 | ], 9 | format: ['esm', 'cjs'], 10 | external: ['next', 'typescript'], 11 | dts: true, 12 | target: 'node18', 13 | }); 14 | -------------------------------------------------------------------------------- /packages/mdx/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | resolve: { 5 | alias: { 6 | '@/': new URL('./src/', import.meta.url).pathname, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/openapi/css/preset.css: -------------------------------------------------------------------------------- 1 | @source '../dist/**/*.js'; 2 | -------------------------------------------------------------------------------- /packages/openapi/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import next from 'eslint-config-custom/next.js'; 2 | 3 | export default [ 4 | { 5 | ignores: ['dist/', 'node_modules/', 'test/out/'], 6 | }, 7 | ...next, 8 | { 9 | rules: { 10 | // commonjs compatibility 11 | 'import/no-named-as-default-member': 'off', 12 | 'no-console': 'off', 13 | // some arrays will not be changed 14 | 'react/no-array-index-key': 'off', 15 | }, 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /packages/openapi/src/icons.tsx: -------------------------------------------------------------------------------- 1 | export * from 'fumadocs-ui/internal/icons'; 2 | -------------------------------------------------------------------------------- /packages/openapi/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generate-file'; 2 | export * from './types'; 3 | export type { MediaAdapter } from './media/adapter'; 4 | -------------------------------------------------------------------------------- /packages/openapi/src/render/heading.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { Heading } from 'fumadocs-ui/components/heading'; 3 | import type { RenderContext } from '@/types'; 4 | 5 | export function heading( 6 | depth: number, 7 | text: string, 8 | ctx: RenderContext, 9 | children: ReactNode = text, 10 | ): ReactNode { 11 | const id = ctx.slugger.slug(text); 12 | 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/openapi/src/requests/index.ts: -------------------------------------------------------------------------------- 1 | import type { CodeSample } from '@/render/operation'; 2 | import * as CURL from '@/requests/curl'; 3 | import * as JS from '@/requests/javascript'; 4 | import * as Go from '@/requests/go'; 5 | import * as Python from '@/requests/python'; 6 | 7 | export const defaultSamples: CodeSample[] = [ 8 | { 9 | label: 'cURL', 10 | source: CURL.generator, 11 | lang: 'bash', 12 | }, 13 | { 14 | label: 'JavaScript', 15 | source: JS.generator, 16 | lang: 'js', 17 | }, 18 | { 19 | label: 'Go', 20 | source: Go.generator, 21 | lang: 'go', 22 | }, 23 | { 24 | label: 'Python', 25 | source: Python.generator, 26 | lang: 'python', 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /packages/openapi/src/scalar/index.tsx: -------------------------------------------------------------------------------- 1 | import type { MethodInformation, RenderContext } from '@/types'; 2 | import dynamic from 'next/dynamic'; 3 | 4 | const Client = dynamic(() => import('./client')); 5 | 6 | export function APIPlayground({ 7 | path, 8 | method, 9 | ctx, 10 | }: { 11 | path: string; 12 | method: MethodInformation; 13 | ctx: RenderContext; 14 | }) { 15 | return ( 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/openapi/src/server/create-method.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | MethodInformation, 3 | OperationObject, 4 | PathItemObject, 5 | } from '@/types'; 6 | import type { NoReference } from '@/utils/schema'; 7 | 8 | /** 9 | * Summarize method endpoint information 10 | */ 11 | export function createMethod( 12 | method: string, 13 | path: NoReference, 14 | operation: NoReference, 15 | ): MethodInformation { 16 | return { 17 | description: path.description, 18 | summary: path.summary, 19 | ...operation, 20 | parameters: [...(operation.parameters ?? []), ...(path.parameters ?? [])], 21 | method: method.toUpperCase(), 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /packages/openapi/src/server/create.tsx: -------------------------------------------------------------------------------- 1 | import type { ApiPageProps } from '@/render/api-page'; 2 | import { createProxy } from '@/server/proxy'; 3 | 4 | export type OpenAPIOptions = Omit, 'document'>; 5 | 6 | export interface OpenAPIServer { 7 | getAPIPageProps: (from: ApiPageProps) => ApiPageProps; 8 | createProxy: typeof createProxy; 9 | } 10 | 11 | export function createOpenAPI(options: OpenAPIOptions = {}): OpenAPIServer { 12 | return { 13 | createProxy, 14 | getAPIPageProps(props) { 15 | return { 16 | ...options, 17 | ...props, 18 | }; 19 | }, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/openapi/src/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create'; 2 | export * from './source-api'; 3 | -------------------------------------------------------------------------------- /packages/openapi/src/ui/lazy.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import dynamic from 'next/dynamic'; 3 | 4 | export const CodeExampleProvider = dynamic(() => 5 | import('./contexts/code-example').then((mod) => mod.CodeExampleProvider), 6 | ); 7 | export const CodeExample = dynamic(() => 8 | import('./contexts/code-example').then((mod) => mod.CodeExample), 9 | ); 10 | export const CodeExampleSelector = dynamic(() => 11 | import('./contexts/code-example').then((mod) => mod.CodeExampleSelector), 12 | ); 13 | 14 | export const ClientLazy = dynamic(() => import('@/playground/client')); 15 | 16 | export const ApiProvider = dynamic(() => 17 | import('./contexts/api').then((mod) => mod.ApiProvider), 18 | ); 19 | -------------------------------------------------------------------------------- /packages/openapi/src/utils/get-typescript-schema.ts: -------------------------------------------------------------------------------- 1 | import { compile } from '@fumari/json-schema-to-typescript'; 2 | import type { DereferenceMap } from '@/types'; 3 | 4 | export async function getTypescriptSchema( 5 | schema: object, 6 | dereferenceMap: DereferenceMap, 7 | ): Promise { 8 | return compile( 9 | // re-running on the same schema results in error 10 | // because it uses `defineProperty` to define internal references 11 | // we clone the schema to fix this problem 12 | schema, 13 | 'Response', 14 | { 15 | $refOptions: false, 16 | schemaToId: dereferenceMap, 17 | bannerComment: '', 18 | additionalProperties: false, 19 | format: true, 20 | enableConstEnums: false, 21 | }, 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/openapi/src/utils/id-to-title.ts: -------------------------------------------------------------------------------- 1 | export function idToTitle(id: string): string { 2 | let result: string[] = []; 3 | 4 | for (const c of id) { 5 | if (result.length === 0) result.push(c.toLocaleUpperCase()); 6 | // ignore the other parts surrounded with '.', like 'migrations.dev' -> 'dev' 7 | else if (c === '.') result = []; 8 | else if (/^[A-Z]$/.test(c) && result.at(-1) !== ' ') result.push(' ', c); 9 | else if (c === '-') result.push(' '); 10 | else result.push(c); 11 | } 12 | 13 | return result.join(''); 14 | } 15 | -------------------------------------------------------------------------------- /packages/openapi/src/utils/server-url.ts: -------------------------------------------------------------------------------- 1 | export function getUrl(url: string, variables: Record): string { 2 | let out = url; 3 | 4 | for (const [key, value] of Object.entries(variables)) { 5 | out = out.replaceAll(`{${key}}`, value); 6 | } 7 | 8 | return out; 9 | } 10 | -------------------------------------------------------------------------------- /packages/openapi/test/out/museum/operations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Operations 3 | description: Operational information about the museum. 4 | full: true 5 | _openapi: 6 | method: GET 7 | route: /museum-hours 8 | toc: 9 | - depth: 2 10 | title: Get museum hours 11 | url: '#get-museum-hours' 12 | structuredData: 13 | headings: 14 | - content: Get museum hours 15 | id: get-museum-hours 16 | contents: 17 | - content: Get upcoming museum operating hours 18 | heading: get-museum-hours 19 | --- 20 | 21 | {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} 22 | 23 | -------------------------------------------------------------------------------- /packages/openapi/test/out/samples/1.bash: -------------------------------------------------------------------------------- 1 | curl -X GET "http://localhost:8080/hello_world?search=ai" \ 2 | -H "authorization: Bearer" \ 3 | --cookie "mode=light" \ 4 | -H "Content-Type: application/json" \ 5 | -d '{ 6 | "id": "id" 7 | }' -------------------------------------------------------------------------------- /packages/openapi/test/out/samples/1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "io/ioutil" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | url := "http://localhost:8080/hello_world?search=ai" 12 | body := strings.NewReader(`{ 13 | "id": "id" 14 | }`) 15 | req, _ := http.NewRequest("GET", url, body) 16 | req.Header.Add("authorization", "Bearer") 17 | req.Header.Add("Cookie", "mode=light") 18 | req.Header.Add("Content-Type", "application/json") 19 | res, _ := http.DefaultClient.Do(req) 20 | defer res.Body.Close() 21 | body, _ := ioutil.ReadAll(res.Body) 22 | 23 | fmt.Println(res) 24 | fmt.Println(string(body)) 25 | } -------------------------------------------------------------------------------- /packages/openapi/test/out/samples/1.js: -------------------------------------------------------------------------------- 1 | const body = JSON.stringify({ 2 | "id": "id" 3 | }) 4 | 5 | fetch("http://localhost:8080/hello_world?search=ai", { 6 | headers: { 7 | "authorization": "Bearer", 8 | "cookie": "mode=light" 9 | }, 10 | body 11 | }) -------------------------------------------------------------------------------- /packages/openapi/test/out/samples/1.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | url = "http://localhost:8080/hello_world?search=ai" 4 | body = { 5 | "id": "id" 6 | } 7 | response = requests.request("GET", url, json = body, headers = { 8 | "authorization": "Bearer", 9 | "Content-Type": "application/json" 10 | }, cookies = { 11 | "mode": "light" 12 | }) 13 | 14 | print(response.text) -------------------------------------------------------------------------------- /packages/openapi/test/out/unkey/liveness.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Liveness 3 | full: true 4 | _openapi: 5 | toc: 6 | - depth: 2 7 | title: Check Liveness 8 | url: '#check-liveness' 9 | structuredData: 10 | headings: 11 | - content: Check Liveness 12 | id: check-liveness 13 | contents: [] 14 | --- 15 | 16 | -------------------------------------------------------------------------------- /packages/openapi/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "sourceMap": false 8 | }, 9 | "include": ["./src/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/openapi/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"], 6 | "lucide-react": ["./src/icons"] 7 | } 8 | }, 9 | "tsc-alias": { 10 | "resolveFullPaths": true 11 | }, 12 | "exclude": ["node_modules", "dist", "eslint.config.mjs", "tailwind.config.js"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/openapi/types.d.ts: -------------------------------------------------------------------------------- 1 | import 'react/experimental'; 2 | -------------------------------------------------------------------------------- /packages/openapi/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | resolve: { 5 | alias: { 6 | '@/': new URL('./src/', import.meta.url).pathname, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/python/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # fumadocs-python 2 | 3 | ## 0.0.2 4 | 5 | ### Patch Changes 6 | 7 | - b0fa6ac: Fix: Add missing dependency for `griffe_typingdoc` 8 | - Updated dependencies [3372792] 9 | - fumadocs-core@15.3.1 10 | - fumadocs-ui@15.3.1 11 | 12 | ## 0.0.1 13 | 14 | ### Patch Changes 15 | 16 | - 3eef0bd: Initial release 17 | - Updated dependencies [d4d1ba7] 18 | - Updated dependencies [4e62b41] 19 | - Updated dependencies [07cd690] 20 | - fumadocs-ui@15.2.11 21 | - fumadocs-core@15.2.11 22 | -------------------------------------------------------------------------------- /packages/python/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/library.js'; 2 | 3 | export default [ 4 | { 5 | ignores: ['dist/', 'node_modules/', 'test/**/*.output.js'], 6 | }, 7 | ...library, 8 | { 9 | rules: { 10 | 'import/no-named-as-default': 'off', 11 | 'import/no-named-as-default-member': 'off', 12 | '@typescript-eslint/no-explicit-any': 'off', 13 | }, 14 | }, 15 | ]; 16 | -------------------------------------------------------------------------------- /packages/python/fumapy/mksource/__init__.py: -------------------------------------------------------------------------------- 1 | from .document_module import parse_class, parse_function, parse_module 2 | from .json_encoder import CustomEncoder 3 | 4 | 5 | __all__ = ( 6 | "parse_class", 7 | "parse_function", 8 | "parse_module", 9 | "CustomEncoder", 10 | ) 11 | -------------------------------------------------------------------------------- /packages/python/fumapy/mksource/json_encoder.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | import griffe 4 | from _griffe.encoders import _json_encoder_map 5 | 6 | 7 | class CustomEncoder(griffe.JSONEncoder): 8 | def default(self, obj: t.Any) -> t.Any: 9 | """Return a serializable representation of the given object. 10 | 11 | Parameters: 12 | obj: The object to serialize. 13 | 14 | Returns: 15 | A serializable representation. 16 | """ 17 | 18 | try: 19 | if isinstance(obj, griffe.Expr): 20 | return str(obj) 21 | return obj.as_dict(full=self.full) 22 | except AttributeError: 23 | return _json_encoder_map.get(type(obj), super().default)(obj) 24 | -------------------------------------------------------------------------------- /packages/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "fumapy" 3 | version = "0.1.0" 4 | description = "Extract API documentation from source code and docstrings. For the python API extension for FumaDocs." 5 | 6 | authors = [ 7 | { name = "florez", email = "florez@ethz.ch" }, 8 | { name = 'fuma-nama' } 9 | ] 10 | requires-python = ">=3.10" 11 | dependencies = [ 12 | "griffe", 13 | "griffe_typingdoc" 14 | ] 15 | 16 | [project.scripts] 17 | fumapy-generate = "fumapy:generate" 18 | 19 | [build-system] 20 | requires = ["hatchling"] 21 | build-backend = "hatchling.build" 22 | -------------------------------------------------------------------------------- /packages/python/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generated'; 2 | export * from './convert'; 3 | export * from './write'; 4 | -------------------------------------------------------------------------------- /packages/python/styles/preset.css: -------------------------------------------------------------------------------- 1 | @source '../dist/**/*.js'; 2 | 3 | @theme inline { 4 | --color-fdpy-func: oklch(79.2% 0.209 151.711); 5 | --color-fdpy-class: red; 6 | --color-fdpy-attribute: oklch(74% 0.238 322.16); 7 | --color-fdpy-method: blue; 8 | } 9 | -------------------------------------------------------------------------------- /packages/python/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/python/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | entry: ['./src/index.ts', './src/components/index.tsx'], 5 | format: 'esm', 6 | dts: true, 7 | target: 'es2022', 8 | external: ['react'], 9 | }); 10 | -------------------------------------------------------------------------------- /packages/python/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | resolve: { 5 | alias: { 6 | '@/': new URL('./src/', import.meta.url).pathname, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "allowJs": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "inlineSources": false, 12 | "isolatedModules": true, 13 | "moduleResolution": "bundler", 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "resolveJsonModule": true, 17 | "preserveWatchOutput": true, 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "strictNullChecks": true 21 | }, 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "plugins": [{ "name": "next" }], 7 | "declaration": false, 8 | "declarationMap": false, 9 | "incremental": true, 10 | "moduleResolution": "bundler", 11 | "jsx": "preserve", 12 | "lib": ["dom", "dom.iterable", "esnext"], 13 | "module": "esnext", 14 | "noEmit": true, 15 | "allowJs": true, 16 | "target": "ES2020" 17 | }, 18 | "include": ["next-env.d.ts", "src"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT" 6 | } 7 | -------------------------------------------------------------------------------- /packages/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "noEmit": true, 7 | "jsx": "react-jsx", 8 | "lib": ["ESNext", "DOM"], 9 | "module": "ESNext", 10 | "target": "ES2020" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/twoslash/README.md: -------------------------------------------------------------------------------- 1 | # Fumadocs Twoslash 2 | 3 | Use Typescript Twolash in Fumadocs. 4 | -------------------------------------------------------------------------------- /packages/twoslash/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/library.js'; 2 | 3 | export default [ 4 | ...library, 5 | { 6 | ignores: ['dist/', 'node_modules/', '*.test.ts'], 7 | }, 8 | ]; 9 | -------------------------------------------------------------------------------- /packages/twoslash/src/ui/cn.ts: -------------------------------------------------------------------------------- 1 | export { twMerge as cn } from 'tailwind-merge'; 2 | -------------------------------------------------------------------------------- /packages/twoslash/src/ui/index.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | export * from './popup'; 3 | -------------------------------------------------------------------------------- /packages/twoslash/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/twoslash/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | dts: true, 5 | target: 'es6', 6 | format: 'esm', 7 | entry: ['src/index.ts', 'src/cache-fs.ts', 'src/ui/index.ts'], 8 | }); 9 | -------------------------------------------------------------------------------- /packages/typescript/README.md: -------------------------------------------------------------------------------- 1 | # Fumadocs Typescript 2 | 3 | Typescript Integration for Fumadocs. 4 | -------------------------------------------------------------------------------- /packages/typescript/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import library from 'eslint-config-custom/library.js'; 2 | 3 | export default [ 4 | { 5 | ignores: ['dist/', 'node_modules', '*.test.ts'], 6 | }, 7 | ...library, 8 | { 9 | rules: { 10 | // typescript is CommonJS 11 | 'import/no-named-as-default-member': 'off', 12 | }, 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /packages/typescript/src/create-project.ts: -------------------------------------------------------------------------------- 1 | import { Project } from 'ts-morph'; 2 | 3 | export interface TypescriptConfig { 4 | files?: string[]; 5 | tsconfigPath?: string; 6 | /** A root directory to resolve relative path entries in the config file to. e.g. outDir */ 7 | basePath?: string; 8 | } 9 | 10 | export function createProject(options: TypescriptConfig = {}): Project { 11 | return new Project({ 12 | tsConfigFilePath: options.tsconfigPath ?? './tsconfig.json', 13 | skipAddingFilesFromTsConfig: true, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@/lib/base'; 2 | export * from '@/lib/mdx'; 3 | export * from '@/lib/file'; 4 | export { renderMarkdownToHast } from './markdown'; 5 | export { createProject } from './create-project'; 6 | export * from '@/lib/remark-auto-type-table'; 7 | -------------------------------------------------------------------------------- /packages/typescript/src/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auto-type-table'; 2 | -------------------------------------------------------------------------------- /packages/typescript/test/fixtures/test-2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Player in the room 3 | */ 4 | export interface Player { 5 | /** 6 | * The name of player 7 | * 8 | * @see https://fumadocs.vercel.app 9 | * @defaultValue Henry 10 | */ 11 | name: string; 12 | 13 | /** 14 | * @example 15 | * ```js 16 | * console.log("Hello World") 17 | * ``` 18 | * @remarks `timestamp` 19 | * Returned by API 20 | */ 21 | age: number; 22 | 23 | /** 24 | * @internal 25 | */ 26 | privateValue: string; 27 | } 28 | -------------------------------------------------------------------------------- /packages/typescript/test/fixtures/test.mdx: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/typescript/test/fixtures/test.ts: -------------------------------------------------------------------------------- 1 | export interface Test1 { 2 | name: string; 3 | /** 4 | * @defaultValue 4 5 | */ 6 | age?: number; 7 | } 8 | 9 | export type Test2 = Test1 & { 10 | generic: GenericType; 11 | }; 12 | 13 | export type { Player as Test3 } from './test-2'; 14 | 15 | interface GenericType { 16 | A: A; 17 | B: B; 18 | C: C; 19 | } 20 | -------------------------------------------------------------------------------- /packages/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist", "eslint.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/typescript/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | external: ['server-only', 'fumadocs-ui', 'fumadocs-core', 'react'], 5 | dts: true, 6 | target: 'es6', 7 | format: 'esm', 8 | entry: ['src/index.ts', 'src/ui/index.ts'], 9 | }); 10 | -------------------------------------------------------------------------------- /packages/typescript/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config'; 2 | 3 | export default defineProject({ 4 | resolve: { 5 | alias: { 6 | '@/': new URL('./src/', import.meta.url).pathname, 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/ui/README.md: -------------------------------------------------------------------------------- 1 | # Fumadocs UI 2 | 3 | The Next.js framework for building documentation website. 4 | 5 | [Read Documentation](https://fumadocs.vercel.app/docs/ui) 6 | -------------------------------------------------------------------------------- /packages/ui/css/shadcn.css: -------------------------------------------------------------------------------- 1 | @theme { 2 | --color-fd-background: var(--background); 3 | --color-fd-foreground: var(--foreground); 4 | --color-fd-muted: var(--muted); 5 | --color-fd-muted-foreground: var(--muted-foreground); 6 | --color-fd-popover: var(--popover); 7 | --color-fd-popover-foreground: var(--popover-foreground); 8 | --color-fd-card: var(--card); 9 | --color-fd-card-foreground: var(--card-foreground); 10 | --color-fd-border: var(--border); 11 | --color-fd-primary: var(--primary); 12 | --color-fd-primary-foreground: var(--primary-foreground); 13 | --color-fd-secondary: var(--secondary); 14 | --color-fd-secondary-foreground: var(--secondary-foreground); 15 | --color-fd-accent: var(--accent); 16 | --color-fd-accent-foreground: var(--accent-foreground); 17 | --color-fd-ring: var(--ring); 18 | } 19 | -------------------------------------------------------------------------------- /packages/ui/css/style.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import './neutral.css'; 3 | @import './preset.css'; 4 | 5 | @layer base { 6 | body { 7 | @apply flex flex-col min-h-screen; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import next from 'eslint-config-custom/next.js'; 2 | 3 | export default [ 4 | ...next, 5 | { 6 | rules: { 7 | // for the import hacks 8 | '@typescript-eslint/consistent-type-imports': 'off', 9 | // some arrays like link items won't be changed 10 | 'react/no-array-index-key': 'off', 11 | '@typescript-eslint/unbound-method': 'off', 12 | '@typescript-eslint/restrict-template-expressions': 'off', 13 | 'import/no-relative-packages': 'off', 14 | }, 15 | }, 16 | ]; 17 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/ui/src/components/steps.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | 3 | export function Steps({ children }: { children: ReactNode }) { 4 | return
{children}
; 5 | } 6 | 7 | export function Step({ children }: { children: ReactNode }) { 8 | return
{children}
; 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui/src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | export { twMerge as cn } from 'tailwind-merge'; 2 | -------------------------------------------------------------------------------- /packages/ui/src/utils/is-active.ts: -------------------------------------------------------------------------------- 1 | export function isActive( 2 | url: string, 3 | pathname: string, 4 | nested = true, 5 | ): boolean { 6 | if (url.endsWith('/')) url = url.slice(0, -1); 7 | if (pathname.endsWith('/')) pathname = pathname.slice(0, -1); 8 | 9 | return url === pathname || (nested && pathname.startsWith(`${url}/`)); 10 | } 11 | -------------------------------------------------------------------------------- /packages/ui/src/utils/merge-refs.ts: -------------------------------------------------------------------------------- 1 | import type * as React from 'react'; 2 | 3 | export function mergeRefs( 4 | ...refs: (React.Ref | undefined)[] 5 | ): React.RefCallback { 6 | return (value) => { 7 | refs.forEach((ref) => { 8 | if (typeof ref === 'function') { 9 | ref(value); 10 | } else if (ref) { 11 | ref.current = value; 12 | } 13 | }); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "outDir": "dist", 6 | "rootDir": "src" 7 | }, 8 | "include": ["./src/**/*"], 9 | "exclude": ["./src/_registry"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@/*": ["./src/*"], 6 | "lucide-react": ["./src/icons"] 7 | } 8 | }, 9 | "tsc-alias": { 10 | "resolveFullPaths": true 11 | }, 12 | "exclude": ["node_modules", "dist", "eslint.config.mjs"] 13 | } 14 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - apps/* 4 | - examples/* 5 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | export default { 3 | singleQuote: true, 4 | endOfLine: 'lf', 5 | plugins: ['prettier-plugin-astro'], 6 | }; 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "helpers:pinGitHubActionDigests", 6 | ":preserveSemverRanges", 7 | "group:allNonMajor" 8 | ], 9 | "baseBranches": ["dev"], 10 | "ignoreDeps": [ 11 | "@types/react", 12 | "@types/react-dom", 13 | "flexsearch", 14 | "@types/node", 15 | "typescript", 16 | "lucide-react" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true 4 | }, 5 | "exclude": [] 6 | } 7 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | export default ['packages/*/vitest.config.ts']; 2 | --------------------------------------------------------------------------------