mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
CLI: Support Tanstack Start SPA
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'create-fumadocs-app': patch
|
||||
---
|
||||
|
||||
Support Tanstack Start SPA
|
||||
@@ -1,18 +1,14 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
.DS_Store
|
||||
.cache
|
||||
.env
|
||||
.vercel
|
||||
.output
|
||||
.nitro
|
||||
/build/
|
||||
/api/
|
||||
/server/build
|
||||
/public/build# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
/public/build
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Your favourite docs framework.
|
||||
icon: Rocket
|
||||
---
|
||||
|
||||
Hey there! Fumadocs is a docs framework built for Next.js, but do you know it also works on Tanstack Start?
|
||||
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
|
||||
|
||||
## Heading
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"start": "node dist/server/server.js"
|
||||
"start": "serve dist/client"
|
||||
},
|
||||
"dependencies": {
|
||||
"@orama/orama": "^3.1.16",
|
||||
"@tanstack/react-router": "^1.133.32",
|
||||
"@tanstack/react-router-devtools": "^1.133.34",
|
||||
"@tanstack/react-start": "^1.133.34",
|
||||
@@ -29,6 +30,7 @@
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"serve": "^14.2.5",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
import {
|
||||
SearchDialog,
|
||||
SearchDialogClose,
|
||||
SearchDialogContent,
|
||||
SearchDialogHeader,
|
||||
SearchDialogIcon,
|
||||
SearchDialogInput,
|
||||
SearchDialogList,
|
||||
SearchDialogOverlay,
|
||||
type SharedProps,
|
||||
} from 'fumadocs-ui/components/dialog/search';
|
||||
import { useDocsSearch } from 'fumadocs-core/search/client';
|
||||
import { create } from '@orama/orama';
|
||||
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
||||
|
||||
function initOrama() {
|
||||
return create({
|
||||
schema: { _: 'string' },
|
||||
// https://docs.orama.com/docs/orama-js/supported-languages
|
||||
language: 'english',
|
||||
});
|
||||
}
|
||||
|
||||
export default function DefaultSearchDialog(props: SharedProps) {
|
||||
const { locale } = useI18n(); // (optional) for i18n
|
||||
const { search, setSearch, query } = useDocsSearch({
|
||||
type: 'static',
|
||||
initOrama,
|
||||
locale,
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchDialog
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
isLoading={query.isLoading}
|
||||
{...props}
|
||||
>
|
||||
<SearchDialogOverlay />
|
||||
<SearchDialogContent>
|
||||
<SearchDialogHeader>
|
||||
<SearchDialogIcon />
|
||||
<SearchDialogInput />
|
||||
<SearchDialogClose />
|
||||
</SearchDialogHeader>
|
||||
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
|
||||
</SearchDialogContent>
|
||||
</SearchDialog>
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import * as React from 'react';
|
||||
import appCss from '@/styles/app.css?url';
|
||||
import { RootProvider } from 'fumadocs-ui/provider/tanstack';
|
||||
import SearchDialog from '@/components/search';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
@@ -42,7 +43,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body className="flex flex-col min-h-screen">
|
||||
<RootProvider>{children}</RootProvider>
|
||||
<RootProvider search={{ SearchDialog }}>{children}</RootProvider>
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,7 +10,7 @@ const server = createFromSource(source, {
|
||||
export const Route = createFileRoute('/api/search')({
|
||||
server: {
|
||||
handlers: {
|
||||
GET: async ({ request }) => server.GET(request),
|
||||
GET: () => server.staticGET(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -75,28 +75,35 @@ function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
function transformPageTree(tree: PageTree.Folder): PageTree.Folder {
|
||||
function transform<T extends PageTree.Item | PageTree.Separator>(item: T) {
|
||||
if (typeof item.icon !== 'string') return item;
|
||||
function transformPageTree(root: PageTree.Root): PageTree.Root {
|
||||
function mapNode<T extends PageTree.Node>(item: T): T {
|
||||
if (typeof item.icon === 'string') {
|
||||
item = {
|
||||
...item,
|
||||
icon: (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.icon,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
icon: (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.icon,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
if (item.type === 'folder') {
|
||||
return {
|
||||
...item,
|
||||
index: item.index ? mapNode(item.index) : undefined,
|
||||
children: item.children.map(mapNode),
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
return {
|
||||
...tree,
|
||||
index: tree.index ? transform(tree.index) : undefined,
|
||||
children: tree.children.map((item) => {
|
||||
if (item.type === 'folder') return transformPageTree(item);
|
||||
return transform(item);
|
||||
}),
|
||||
...root,
|
||||
children: root.children.map(mapNode),
|
||||
fallback: root.fallback ? transformPageTree(root.fallback) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,12 +24,13 @@ export default defineConfig({
|
||||
crawlLinks: true,
|
||||
},
|
||||
},
|
||||
|
||||
pages: [
|
||||
{
|
||||
path: '/docs',
|
||||
},
|
||||
{
|
||||
path: '/docs/test',
|
||||
path: '/api/search',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Your favourite docs framework.
|
||||
icon: Rocket
|
||||
---
|
||||
|
||||
Hey there! Fumadocs is a docs framework built for Next.js, but do you know it also works on Tanstack Start?
|
||||
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
|
||||
|
||||
## Heading
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ const templateDir = path.join(dir, '../template');
|
||||
const templates = [
|
||||
'react-router',
|
||||
'tanstack-start',
|
||||
'tanstack-start-spa',
|
||||
'react-router-spa',
|
||||
'waku',
|
||||
['next-mdx', '+next+fuma-docs-mdx'],
|
||||
|
||||
@@ -11,7 +11,8 @@ export interface TemplateInfo {
|
||||
| 'waku'
|
||||
| 'react-router'
|
||||
| 'react-router-spa'
|
||||
| 'tanstack-start';
|
||||
| 'tanstack-start'
|
||||
| 'tanstack-start-spa';
|
||||
label: string;
|
||||
appDir: string;
|
||||
/**
|
||||
@@ -58,6 +59,13 @@ export const templates: TemplateInfo[] = [
|
||||
appDir: 'src',
|
||||
rootProviderPath: 'routes/__root.tsx',
|
||||
},
|
||||
{
|
||||
value: 'tanstack-start-spa',
|
||||
label: 'Tanstack Start SPA: Fumadocs MDX (not RSC)',
|
||||
hint: 'SPA mode allows you to host the site statically, compatible with a CDN.',
|
||||
appDir: 'src',
|
||||
rootProviderPath: 'routes/__root.tsx',
|
||||
},
|
||||
];
|
||||
|
||||
export const depVersions = versionPkg.dependencies;
|
||||
|
||||
@@ -79,6 +79,7 @@ See https://fumadocs.dev/docs/headless/search/orama-cloud for integrating Orama
|
||||
const filePath = {
|
||||
'+next+fuma-docs-mdx': '.next/server/app/static.json.body',
|
||||
'tanstack-start': '.output/public/static.json',
|
||||
'tanstack-start-spa': 'dist/client/static.json',
|
||||
'react-router': 'build/client/static.json',
|
||||
'react-router-spa': 'build/client/static.json',
|
||||
waku: 'dist/public/static.json',
|
||||
|
||||
@@ -48,12 +48,12 @@ export async function rootProvider(
|
||||
name: 'search',
|
||||
initializer: '{{ SearchDialog }}',
|
||||
});
|
||||
file.addImportDeclaration({
|
||||
moduleSpecifier: specifier,
|
||||
defaultImport: 'SearchDialog',
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
file.addImportDeclaration({
|
||||
moduleSpecifier: specifier,
|
||||
defaultImport: 'SearchDialog',
|
||||
});
|
||||
},
|
||||
});
|
||||
await file.save();
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
This is a Tanstack Start application generated with
|
||||
[Create Fumadocs](https://github.com/fuma-nama/fumadocs).
|
||||
|
||||
Run development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: Hello World
|
||||
description: Your favourite docs framework.
|
||||
icon: Rocket
|
||||
---
|
||||
|
||||
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
|
||||
|
||||
## Heading
|
||||
|
||||
Hello World!
|
||||
|
||||
<Cards>
|
||||
<Card
|
||||
title="Learn more about Tanstack Start"
|
||||
href="https://tanstack.com/start"
|
||||
/>
|
||||
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
|
||||
</Cards>
|
||||
|
||||
### CodeBlock
|
||||
|
||||
```ts
|
||||
console.log('Hello World');
|
||||
```
|
||||
|
||||
#### Table
|
||||
|
||||
| Head | Description |
|
||||
| ------------------------------- | ----------------------------------- |
|
||||
| `hello` | Hello World |
|
||||
| very **important** | Hey |
|
||||
| _Surprisingly_ | Fumadocs |
|
||||
| very long text that looks weird | hello world hello world hello world |
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Test
|
||||
description: This is another page
|
||||
---
|
||||
|
||||
Hello World again!
|
||||
|
||||
## Installation
|
||||
|
||||
```npm
|
||||
npm i fumadocs-core fumadocs-ui
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
node_modules
|
||||
|
||||
.DS_Store
|
||||
.cache
|
||||
.vercel
|
||||
.output
|
||||
.nitro
|
||||
/build/
|
||||
/api/
|
||||
/server/build
|
||||
/public/build
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
.tanstack
|
||||
|
||||
src/routeTree.gen.ts
|
||||
.source
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "example-tanstack-start-spa",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"start": "serve dist/client"
|
||||
},
|
||||
"dependencies": {
|
||||
"@orama/orama": "^3.1.16",
|
||||
"@tanstack/react-router": "^1.133.32",
|
||||
"@tanstack/react-router-devtools": "^1.133.34",
|
||||
"@tanstack/react-start": "^1.133.34",
|
||||
"@tanstack/start-static-server-functions": "^1.133.34",
|
||||
"fumadocs-core": "workspace:*",
|
||||
"fumadocs-mdx": "workspace:*",
|
||||
"fumadocs-ui": "workspace:*",
|
||||
"lucide-static": "^0.548.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"vite": "^7.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "^24.9.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"serve": "^14.2.5",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
|
||||
|
||||
export const docs = defineDocs({
|
||||
dir: 'content/docs',
|
||||
});
|
||||
|
||||
export default defineConfig();
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { HomeLayout } from 'fumadocs-ui/layouts/home';
|
||||
|
||||
export function NotFound() {
|
||||
return (
|
||||
<HomeLayout
|
||||
nav={{
|
||||
title: 'Tanstack Start',
|
||||
}}
|
||||
className="text-center py-32 justify-center"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<h1 className="text-6xl font-bold text-fd-muted-foreground">404</h1>
|
||||
<h2 className="text-2xl font-semibold">Page Not Found</h2>
|
||||
<p className="text-fd-muted-foreground max-w-md">
|
||||
The page you are looking for might have been removed, had its name
|
||||
changed, or is temporarily unavailable.
|
||||
</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="mt-4 px-4 py-2 rounded-lg bg-fd-primary text-fd-primary-foreground font-medium text-sm hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
</HomeLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
import {
|
||||
SearchDialog,
|
||||
SearchDialogClose,
|
||||
SearchDialogContent,
|
||||
SearchDialogHeader,
|
||||
SearchDialogIcon,
|
||||
SearchDialogInput,
|
||||
SearchDialogList,
|
||||
SearchDialogOverlay,
|
||||
type SharedProps,
|
||||
} from 'fumadocs-ui/components/dialog/search';
|
||||
import { useDocsSearch } from 'fumadocs-core/search/client';
|
||||
import { create } from '@orama/orama';
|
||||
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
||||
|
||||
function initOrama() {
|
||||
return create({
|
||||
schema: { _: 'string' },
|
||||
// https://docs.orama.com/docs/orama-js/supported-languages
|
||||
language: 'english',
|
||||
});
|
||||
}
|
||||
|
||||
export default function DefaultSearchDialog(props: SharedProps) {
|
||||
const { locale } = useI18n(); // (optional) for i18n
|
||||
const { search, setSearch, query } = useDocsSearch({
|
||||
type: 'static',
|
||||
initOrama,
|
||||
locale,
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchDialog
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
isLoading={query.isLoading}
|
||||
{...props}
|
||||
>
|
||||
<SearchDialogOverlay />
|
||||
<SearchDialogContent>
|
||||
<SearchDialogHeader>
|
||||
<SearchDialogIcon />
|
||||
<SearchDialogInput />
|
||||
<SearchDialogClose />
|
||||
</SearchDialogHeader>
|
||||
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
|
||||
</SearchDialogContent>
|
||||
</SearchDialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
||||
|
||||
export function baseOptions(): BaseLayoutProps {
|
||||
return {
|
||||
nav: {
|
||||
title: 'Tanstack Start',
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
import * as icons from 'lucide-static';
|
||||
import { create, docs } from '@/.source';
|
||||
|
||||
export const source = loader({
|
||||
source: await create.sourceAsync(docs.doc, docs.meta),
|
||||
baseUrl: '/docs',
|
||||
icon(icon) {
|
||||
if (!icon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (icon in icons) return icons[icon as keyof typeof icons];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { createRouter as createTanStackRouter } from '@tanstack/react-router';
|
||||
import { routeTree } from './routeTree.gen';
|
||||
import { NotFound } from '@/components/not-found';
|
||||
|
||||
export function getRouter() {
|
||||
return createTanStackRouter({
|
||||
routeTree,
|
||||
defaultPreload: 'intent',
|
||||
scrollRestoration: true,
|
||||
defaultNotFoundComponent: NotFound,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
createRootRoute,
|
||||
HeadContent,
|
||||
Outlet,
|
||||
Scripts,
|
||||
} from '@tanstack/react-router';
|
||||
import * as React from 'react';
|
||||
import appCss from '@/styles/app.css?url';
|
||||
import { RootProvider } from 'fumadocs-ui/provider/tanstack';
|
||||
import SearchDialog from '@/components/search';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{
|
||||
charSet: 'utf-8',
|
||||
},
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1',
|
||||
},
|
||||
{
|
||||
title: 'Fumadocs on TanStack Start',
|
||||
},
|
||||
],
|
||||
links: [{ rel: 'stylesheet', href: appCss }],
|
||||
}),
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<RootDocument>
|
||||
<Outlet />
|
||||
</RootDocument>
|
||||
);
|
||||
}
|
||||
|
||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html suppressHydrationWarning>
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body className="flex flex-col min-h-screen">
|
||||
<RootProvider search={{ SearchDialog }}>{children}</RootProvider>
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { source } from '@/lib/source';
|
||||
import { createFromSource } from 'fumadocs-core/search/server';
|
||||
|
||||
const server = createFromSource(source, {
|
||||
// https://docs.orama.com/docs/orama-js/supported-languages
|
||||
language: 'english',
|
||||
});
|
||||
|
||||
export const Route = createFileRoute('/api/search')({
|
||||
server: {
|
||||
handlers: {
|
||||
GET: () => server.staticGET(),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
import { createFileRoute, notFound } from '@tanstack/react-router';
|
||||
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||
import { createServerFn } from '@tanstack/react-start';
|
||||
import { source } from '@/lib/source';
|
||||
import type * as PageTree from 'fumadocs-core/page-tree';
|
||||
import { useMemo } from 'react';
|
||||
import { docs } from '@/.source';
|
||||
import {
|
||||
DocsBody,
|
||||
DocsDescription,
|
||||
DocsPage,
|
||||
DocsTitle,
|
||||
} from 'fumadocs-ui/page';
|
||||
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
||||
import { createClientLoader } from 'fumadocs-mdx/runtime/vite';
|
||||
import { baseOptions } from '@/lib/layout.shared';
|
||||
import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions';
|
||||
|
||||
export const Route = createFileRoute('/docs/$')({
|
||||
component: Page,
|
||||
loader: async ({ params }) => {
|
||||
const slugs = params._splat?.split('/') ?? [];
|
||||
const data = await loader({ data: slugs });
|
||||
await clientLoader.preload(data.path);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
const loader = createServerFn({
|
||||
method: 'GET',
|
||||
})
|
||||
.inputValidator((slugs: string[]) => slugs)
|
||||
.middleware([staticFunctionMiddleware])
|
||||
.handler(async ({ data: slugs }) => {
|
||||
const page = source.getPage(slugs);
|
||||
if (!page) throw notFound();
|
||||
|
||||
return {
|
||||
tree: source.pageTree as object,
|
||||
path: page.path,
|
||||
};
|
||||
});
|
||||
|
||||
const clientLoader = createClientLoader(docs.doc, {
|
||||
id: 'docs',
|
||||
component({ toc, frontmatter, default: MDX }) {
|
||||
return (
|
||||
<DocsPage toc={toc}>
|
||||
<DocsTitle>{frontmatter.title}</DocsTitle>
|
||||
<DocsDescription>{frontmatter.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<MDX
|
||||
components={{
|
||||
...defaultMdxComponents,
|
||||
}}
|
||||
/>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function Page() {
|
||||
const data = Route.useLoaderData();
|
||||
const Content = clientLoader.getComponent(data.path);
|
||||
const tree = useMemo(
|
||||
() => transformPageTree(data.tree as PageTree.Folder),
|
||||
[data.tree],
|
||||
);
|
||||
|
||||
return (
|
||||
<DocsLayout {...baseOptions()} tree={tree}>
|
||||
<Content />
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
|
||||
function transformPageTree(root: PageTree.Root): PageTree.Root {
|
||||
function mapNode<T extends PageTree.Node>(item: T): T {
|
||||
if (typeof item.icon === 'string') {
|
||||
item = {
|
||||
...item,
|
||||
icon: (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.icon,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === 'folder') {
|
||||
return {
|
||||
...item,
|
||||
index: item.index ? mapNode(item.index) : undefined,
|
||||
children: item.children.map(mapNode),
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
return {
|
||||
...root,
|
||||
children: root.children.map(mapNode),
|
||||
fallback: root.fallback ? transformPageTree(root.fallback) : undefined,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createFileRoute, Link } from '@tanstack/react-router';
|
||||
import { HomeLayout } from 'fumadocs-ui/layouts/home';
|
||||
import { baseOptions } from '@/lib/layout.shared';
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<HomeLayout {...baseOptions()} className="text-center py-32 justify-center">
|
||||
<h1 className="font-medium text-xl mb-4">Fumadocs on Tanstack Start.</h1>
|
||||
<Link
|
||||
to="/docs/$"
|
||||
params={{
|
||||
_splat: '',
|
||||
}}
|
||||
className="px-3 py-2 rounded-lg bg-fd-primary text-fd-primary-foreground font-medium text-sm mx-auto"
|
||||
>
|
||||
Open Docs
|
||||
</Link>
|
||||
</HomeLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
@import 'tailwindcss';
|
||||
@import 'fumadocs-ui/css/neutral.css';
|
||||
@import 'fumadocs-ui/css/preset.css';
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"types": ["vite/client"],
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "ES2022",
|
||||
"allowJs": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@/.source": [".source"]
|
||||
},
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import tsConfigPaths from 'vite-tsconfig-paths';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import mdx from 'fumadocs-mdx/vite';
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
plugins: [
|
||||
mdx(await import('./source.config')),
|
||||
tailwindcss(),
|
||||
tsConfigPaths({
|
||||
projects: ['./tsconfig.json'],
|
||||
}),
|
||||
tanstackStart({
|
||||
spa: {
|
||||
enabled: true,
|
||||
prerender: {
|
||||
outputPath: 'index.html',
|
||||
enabled: true,
|
||||
crawlLinks: true,
|
||||
},
|
||||
},
|
||||
|
||||
pages: [
|
||||
{
|
||||
path: '/docs',
|
||||
},
|
||||
{
|
||||
path: '/api/search',
|
||||
},
|
||||
],
|
||||
}),
|
||||
react(),
|
||||
],
|
||||
});
|
||||
@@ -4,7 +4,7 @@ description: Your favourite docs framework.
|
||||
icon: Rocket
|
||||
---
|
||||
|
||||
Hey there! Fumadocs is a docs framework built for Next.js, but do you know it also works on Tanstack Start?
|
||||
Hey there! Fumadocs is the docs framework that also works on Tanstack Start!
|
||||
|
||||
## Heading
|
||||
|
||||
|
||||
Generated
+9
-54
Reference in New Issue
Block a user