-
Notifications
You must be signed in to change notification settings - Fork 2
Set up Fumadocs documentation site with Cloudflare Workers deploy #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
89cb2db
9965e79
8155a56
67596c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # deps | ||
| /node_modules | ||
|
|
||
| # generated content | ||
| .source | ||
|
|
||
| # test & build | ||
| /coverage | ||
| /.next/ | ||
| /out/ | ||
| /build | ||
| *.tsbuildinfo | ||
|
|
||
| # misc | ||
| .DS_Store | ||
| *.pem | ||
| /.pnp | ||
| .pnp.js | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
|
|
||
| # others | ||
| .env*.local | ||
| .vercel | ||
| next-env.d.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| This is a Next.js application generated with | ||
| [Create Fumadocs](https://github.com/fuma-nama/fumadocs). | ||
|
|
||
| It is a Next.js app with [Static Export](https://nextjs.org/docs/app/guides/static-exports) configured. | ||
|
|
||
| Run development server: | ||
|
|
||
| ```bash | ||
| npm run dev | ||
| # or | ||
| pnpm dev | ||
| # or | ||
| yarn dev | ||
| ``` | ||
|
|
||
| Open http://localhost:3000 with your browser to see the result. | ||
|
|
||
| ## Explore | ||
|
|
||
| In the project, you can see: | ||
|
|
||
| - `lib/source.ts`: Code for content source adapter, [`loader()`](https://fumadocs.dev/docs/headless/source-api) provides the interface to access your content. | ||
| - `lib/layout.shared.tsx`: Shared options for layouts, optional but preferred to keep. | ||
|
|
||
| | Route | Description | | ||
| | ------------------------- | ------------------------------------------------------ | | ||
| | `app/(home)` | The route group for your landing page and other pages. | | ||
| | `app/docs` | The documentation layout and pages. | | ||
| | `app/api/search/route.ts` | The Route Handler for search. | | ||
|
|
||
| ### Fumadocs MDX | ||
|
|
||
| A `source.config.ts` config file has been included, you can customise different options like frontmatter schema. | ||
|
|
||
| Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details. | ||
|
|
||
| ## Learn More | ||
|
|
||
| To learn more about Next.js and Fumadocs, take a look at the following | ||
| resources: | ||
|
|
||
| - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js | ||
| features and API. | ||
| - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | ||
| - [Fumadocs](https://fumadocs.dev) - learn about Fumadocs |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { HomeLayout } from 'fumadocs-ui/layouts/home'; | ||
| import { baseOptions } from '@/lib/layout.shared'; | ||
|
|
||
| export default function Layout({ children }: LayoutProps<'/'>) { | ||
| return <HomeLayout {...baseOptions()}>{children}</HomeLayout>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import Link from 'next/link'; | ||
|
|
||
| export default function HomePage() { | ||
| return ( | ||
| <div className="flex flex-col justify-center text-center flex-1"> | ||
| <h1 className="text-2xl font-bold mb-4">Hello World</h1> | ||
| <p> | ||
| You can open{' '} | ||
| <Link href="/docs" className="font-medium underline"> | ||
| /docs | ||
| </Link>{' '} | ||
| and see the documentation. | ||
| </p> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { source } from '@/lib/source'; | ||
| import { createFromSource } from 'fumadocs-core/search/server'; | ||
|
|
||
| export const revalidate = false; | ||
|
|
||
| export const { staticGET: GET } = createFromSource(source, { | ||
| // https://docs.orama.com/docs/orama-js/supported-languages | ||
| language: 'english', | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||||||||||||||
| import { getPageImage, getPageMarkdownUrl, source } from '@/lib/source'; | ||||||||||||||||||
| import { | ||||||||||||||||||
| DocsBody, | ||||||||||||||||||
| DocsDescription, | ||||||||||||||||||
| DocsPage, | ||||||||||||||||||
| DocsTitle, | ||||||||||||||||||
| MarkdownCopyButton, | ||||||||||||||||||
| ViewOptionsPopover, | ||||||||||||||||||
| } from 'fumadocs-ui/layouts/docs/page'; | ||||||||||||||||||
| import { notFound } from 'next/navigation'; | ||||||||||||||||||
| import { getMDXComponents } from '@/components/mdx'; | ||||||||||||||||||
| import type { Metadata } from 'next'; | ||||||||||||||||||
| import { createRelativeLink } from 'fumadocs-ui/mdx'; | ||||||||||||||||||
| import { gitConfig } from '@/lib/shared'; | ||||||||||||||||||
|
|
||||||||||||||||||
| export default async function Page(props: PageProps<'/docs/[[...slug]]'>) { | ||||||||||||||||||
| const params = await props.params; | ||||||||||||||||||
| const page = source.getPage(params.slug); | ||||||||||||||||||
| if (!page) notFound(); | ||||||||||||||||||
|
|
||||||||||||||||||
| const MDX = page.data.body; | ||||||||||||||||||
| const markdownUrl = getPageMarkdownUrl(page).url; | ||||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <DocsPage toc={page.data.toc} full={page.data.full}> | ||||||||||||||||||
| <DocsTitle>{page.data.title}</DocsTitle> | ||||||||||||||||||
| <DocsDescription className="mb-0">{page.data.description}</DocsDescription> | ||||||||||||||||||
| <div className="flex flex-row gap-2 items-center border-b pb-6"> | ||||||||||||||||||
| <MarkdownCopyButton markdownUrl={markdownUrl} /> | ||||||||||||||||||
| <ViewOptionsPopover | ||||||||||||||||||
| markdownUrl={markdownUrl} | ||||||||||||||||||
| githubUrl={`https://github.com/${gitConfig.user}/${gitConfig.repo}/blob/${gitConfig.branch}/content/docs/${page.path}`} | ||||||||||||||||||
| /> | ||||||||||||||||||
|
Comment on lines
+30
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Fix the repository path in the GitHub source link. The docs files in this PR live under Suggested fix <ViewOptionsPopover
markdownUrl={markdownUrl}
- githubUrl={`https://github.com/${gitConfig.user}/${gitConfig.repo}/blob/${gitConfig.branch}/content/docs/${page.path}`}
+ githubUrl={`https://github.com/${gitConfig.user}/${gitConfig.repo}/blob/${gitConfig.branch}/docs/content/docs/${page.path}`}
/>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| </div> | ||||||||||||||||||
| <DocsBody> | ||||||||||||||||||
| <MDX | ||||||||||||||||||
| components={getMDXComponents({ | ||||||||||||||||||
| // this allows you to link to other pages with relative file paths | ||||||||||||||||||
| a: createRelativeLink(source, page), | ||||||||||||||||||
| })} | ||||||||||||||||||
| /> | ||||||||||||||||||
| </DocsBody> | ||||||||||||||||||
| </DocsPage> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export async function generateStaticParams() { | ||||||||||||||||||
| return source.generateParams(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export async function generateMetadata(props: PageProps<'/docs/[[...slug]]'>): Promise<Metadata> { | ||||||||||||||||||
| const params = await props.params; | ||||||||||||||||||
| const page = source.getPage(params.slug); | ||||||||||||||||||
| if (!page) notFound(); | ||||||||||||||||||
|
|
||||||||||||||||||
| return { | ||||||||||||||||||
| title: page.data.title, | ||||||||||||||||||
| description: page.data.description, | ||||||||||||||||||
| openGraph: { | ||||||||||||||||||
| images: getPageImage(page).url, | ||||||||||||||||||
| }, | ||||||||||||||||||
| }; | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { source } from '@/lib/source'; | ||
| import { DocsLayout } from 'fumadocs-ui/layouts/docs'; | ||
| import { baseOptions } from '@/lib/layout.shared'; | ||
|
|
||
| export default function Layout({ children }: LayoutProps<'/docs'>) { | ||
| return ( | ||
| <DocsLayout tree={source.getPageTree()} {...baseOptions()}> | ||
| {children} | ||
| </DocsLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| @import 'tailwindcss'; | ||
| @import 'fumadocs-ui/css/neutral.css'; | ||
| @import 'fumadocs-ui/css/preset.css'; | ||
|
|
||
| html { | ||
| scrollbar-gutter: stable; | ||
| } | ||
|
|
||
| html > body[data-scroll-locked] { | ||
| margin-right: 0px !important; | ||
| --removed-body-scroll-bar-size: 0px !important; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||||||||||||||||||||||||||
| import type { Metadata } from 'next'; | ||||||||||||||||||||||||||||||
| import { Inter } from 'next/font/google'; | ||||||||||||||||||||||||||||||
| import { Provider } from '@/components/provider'; | ||||||||||||||||||||||||||||||
| import './global.css'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const inter = Inter({ | ||||||||||||||||||||||||||||||
| subsets: ['latin'], | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Set NEXT_PUBLIC_SITE_URL (e.g. https://docs.example.com) in CI so OpenGraph | ||||||||||||||||||||||||||||||
| // image URLs resolve to the deployed origin instead of localhost. | ||||||||||||||||||||||||||||||
| export const metadata: Metadata = { | ||||||||||||||||||||||||||||||
| metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'), | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Guard
🛡️ Proposed change+function getMetadataBase() {
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL?.trim();
+
+ try {
+ return new URL(siteUrl || 'http://localhost:3000');
+ } catch {
+ return new URL('http://localhost:3000');
+ }
+}
+
export const metadata: Metadata = {
- metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'),
+ metadataBase: getMetadataBase(),
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export default function Layout({ children }: LayoutProps<'/'>) { | ||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <html lang="en" className={inter.className} suppressHydrationWarning> | ||||||||||||||||||||||||||||||
| <body className="flex flex-col min-h-screen"> | ||||||||||||||||||||||||||||||
| <Provider>{children}</Provider> | ||||||||||||||||||||||||||||||
| </body> | ||||||||||||||||||||||||||||||
| </html> | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { getLLMText, source } from '@/lib/source'; | ||
|
|
||
| export const revalidate = false; | ||
|
|
||
| export async function GET() { | ||
| const scan = source.getPages().map(getLLMText); | ||
| const scanned = await Promise.all(scan); | ||
|
|
||
| return new Response(scanned.join('\n\n')); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { getLLMText, getPageMarkdownUrl, source } from '@/lib/source'; | ||
| import { notFound } from 'next/navigation'; | ||
|
|
||
| export const revalidate = false; | ||
|
|
||
| export async function GET(_req: Request, { params }: RouteContext<'/llms.mdx/docs/[[...slug]]'>) { | ||
| const { slug } = await params; | ||
| // remove the appended "content.md" | ||
| const page = source.getPage(slug?.slice(0, -1)); | ||
| if (!page) notFound(); | ||
|
|
||
| return new Response(await getLLMText(page), { | ||
| headers: { | ||
| 'Content-Type': 'text/markdown', | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| export function generateStaticParams() { | ||
| return source.getPages().map((page) => ({ | ||
| slug: getPageMarkdownUrl(page).segments, | ||
| })); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { source } from '@/lib/source'; | ||
| import { llms } from 'fumadocs-core/source'; | ||
|
|
||
| export const revalidate = false; | ||
|
|
||
| export function GET() { | ||
| return new Response(llms(source).index()); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { getPageImage, source } from '@/lib/source'; | ||
| import { notFound } from 'next/navigation'; | ||
| import { ImageResponse } from 'next/og'; | ||
| import { generate as DefaultImage } from 'fumadocs-ui/og'; | ||
| import { appName } from '@/lib/shared'; | ||
|
|
||
| export const revalidate = false; | ||
|
|
||
| export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...slug]'>) { | ||
| const { slug } = await params; | ||
| const page = source.getPage(slug.slice(0, -1)); | ||
| if (!page) notFound(); | ||
|
|
||
| return new ImageResponse( | ||
| <DefaultImage title={page.data.title} description={page.data.description} site={appName} />, | ||
| { | ||
| width: 1200, | ||
| height: 630, | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| export function generateStaticParams() { | ||
| return source.getPages().map((page) => ({ | ||
| lang: page.locale, | ||
| slug: getPageImage(page).segments, | ||
| })); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import defaultMdxComponents from 'fumadocs-ui/mdx'; | ||
| import type { MDXComponents } from 'mdx/types'; | ||
|
|
||
| export function getMDXComponents(components?: MDXComponents) { | ||
| return { | ||
| ...defaultMdxComponents, | ||
| ...components, | ||
| } satisfies MDXComponents; | ||
| } | ||
|
|
||
| export const useMDXComponents = getMDXComponents; | ||
|
|
||
| declare global { | ||
| type MDXProvidedComponents = ReturnType<typeof getMDXComponents>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| 'use client'; | ||
| import SearchDialog from '@/components/search'; | ||
| import { RootProvider } from 'fumadocs-ui/provider/next'; | ||
| import { type ReactNode } from 'react'; | ||
|
|
||
| export function Provider({ children }: { children: ReactNode }) { | ||
| return <RootProvider search={{ SearchDialog }}>{children}</RootProvider>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| '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 { oramaStaticClient } from 'fumadocs-core/search/client/orama-static'; | ||
| 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({ | ||
| client: oramaStaticClient({ | ||
| 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> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Replace the scaffold placeholder before shipping.
The root docs landing page currently renders
Hello World, which looks unfinished for a production docs site. Either redirect/to/docsor swap this for branded copy that matches the migrated documentation.🤖 Prompt for AI Agents