diff --git a/docs/next.config.mjs b/docs/next.config.mjs index ae0a6d2..4a82553 100644 --- a/docs/next.config.mjs +++ b/docs/next.config.mjs @@ -4,15 +4,12 @@ const withMDX = createMDX(); /** @type {import('next').NextConfig} */ const config = { + output: 'export', reactStrictMode: true, - async rewrites() { - return [ - { - source: '/:path*.mdx', - destination: '/llms.mdx/:path*', - }, - ]; - }, + // Use trailing slashes to avoid conflicts with route handlers that have file extensions + trailingSlash: true, + // Note: rewrites are not supported with static export + // The /llms.mdx route will be pre-rendered as static files }; export default withMDX(config); diff --git a/docs/package.json b/docs/package.json index 16ba5fb..d3554d4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -2,8 +2,9 @@ "name": "docs", "version": "0.0.0", "private": true, + "type": "module", "scripts": { - "build": "next build", + "build": "next build && node scripts/post-export.js", "dev": "next dev", "start": "next start", "types:check": "fumadocs-mdx && next typegen && tsc --noEmit", diff --git a/docs/scripts/post-export.js b/docs/scripts/post-export.js new file mode 100755 index 0000000..08a761f --- /dev/null +++ b/docs/scripts/post-export.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +/** + * Post-export script to copy English content to root directory + * This allows the default language (en) to be accessible without language prefix + */ + +import { cpSync, existsSync, mkdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const outDir = join(__dirname, '../out'); +const enDir = join(outDir, 'en'); + +console.log('Copying English content to root directory...'); + +if (!existsSync(enDir)) { + console.error('Error: /en directory not found in output'); + process.exit(1); +} + +// Get all items in /en directory +const fs = await import('node:fs/promises'); +const items = await fs.readdir(enDir); + +// Copy each item to root, excluding already existing root items +for (const item of items) { + const source = join(enDir, item); + const dest = join(outDir, item); + + // Skip if item already exists at root (like _next, api, etc.) + if (existsSync(dest)) { + console.log(`Skipping ${item} (already exists at root)`); + continue; + } + + try { + cpSync(source, dest, { recursive: true }); + console.log(`Copied ${item}`); + } catch (error) { + console.error(`Error copying ${item}:`, error.message); + } +} + +console.log('English content copied to root directory successfully!'); diff --git a/docs/src/app/[lang]/(docs)/layout.tsx b/docs/src/app/[lang]/(docs)/layout.tsx index 1527d11..4ac7c24 100644 --- a/docs/src/app/[lang]/(docs)/layout.tsx +++ b/docs/src/app/[lang]/(docs)/layout.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import { source } from '@/lib/source'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; @@ -5,7 +6,10 @@ import { baseOptions } from '@/lib/layout.shared'; export default async function Layout({ children, params, -}: LayoutProps<'/[lang]/[[...slug]]'>) { +}: { + children: ReactNode; + params: Promise<{ lang: string; slug?: string[] }>; +}) { const resolvedParams = await params; const locale = resolvedParams.lang; return ( diff --git a/docs/src/app/[lang]/layout.tsx b/docs/src/app/[lang]/layout.tsx index cb1ec6d..a3a9ff1 100644 --- a/docs/src/app/[lang]/layout.tsx +++ b/docs/src/app/[lang]/layout.tsx @@ -24,5 +24,16 @@ export default async function Layout({ const { lang } = await params; const locale = isLocale(lang) ? lang : i18n.defaultLanguage; - return {children}; + return ( + + {children} + + ); } diff --git a/docs/src/app/api/search/route.ts b/docs/src/app/api/search/route.ts index 7ba7e82..21cbcce 100644 --- a/docs/src/app/api/search/route.ts +++ b/docs/src/app/api/search/route.ts @@ -1,7 +1,14 @@ import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; -export const { GET } = createFromSource(source, { - // https://docs.orama.com/docs/orama-js/supported-languages - language: 'english', +// statically cached for static export +export const revalidate = false; + +// Configure language support for both English and Chinese +export const { staticGET: GET } = createFromSource(source, { + localeMap: { + en: { language: 'english' }, + // Chinese is not natively supported by Orama, use English tokenizer for zh + zh: { language: 'english' }, + }, }); diff --git a/docs/src/app/llms-full.txt/route.ts b/docs/src/app/llms-full.txt/route.ts deleted file mode 100644 index d494d2c..0000000 --- a/docs/src/app/llms-full.txt/route.ts +++ /dev/null @@ -1,10 +0,0 @@ -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')); -} diff --git a/docs/src/app/llms.mdx/[[...slug]]/route.ts b/docs/src/app/llms.mdx/[[...slug]]/route.ts deleted file mode 100644 index 1281482..0000000 --- a/docs/src/app/llms.mdx/[[...slug]]/route.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { i18n, resolveLocaleFromSlug, stripLocaleFromSlug } from '@/lib/i18n'; -import { getLLMText, source } from '@/lib/source'; -import { notFound } from 'next/navigation'; - -export const revalidate = false; - -export async function GET(_req: Request, { params }: RouteContext<'/llms.mdx/[[...slug]]'>) { - const { slug } = await params; - const locale = resolveLocaleFromSlug(slug); - const slugs = stripLocaleFromSlug(slug); - const page = source.getPage(slugs, locale); - if (!page) notFound(); - - return new Response(await getLLMText(page), { - headers: { - 'Content-Type': 'text/markdown', - }, - }); -} - -export function generateStaticParams() { - return source.getLanguages().flatMap(({ language, pages }) => - pages.map((page) => ({ - slug: language === i18n.defaultLanguage ? page.slugs : [language, ...page.slugs], - })), - ); -} diff --git a/docs/src/app/og/docs/[...slug]/route.tsx b/docs/src/app/og/docs/[...slug]/route.tsx deleted file mode 100644 index fcf1863..0000000 --- a/docs/src/app/og/docs/[...slug]/route.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { resolveLocaleFromSlug, stripLocaleFromSlug } from '@/lib/i18n'; -import { getPageImage, source } from '@/lib/source'; -import { notFound } from 'next/navigation'; -import { ImageResponse } from 'next/og'; -import { generate as DefaultImage } from 'fumadocs-ui/og'; - -export const revalidate = false; - -export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...slug]'>) { - const { slug } = await params; - const pageSlug = slug.slice(0, -1); - const locale = resolveLocaleFromSlug(pageSlug); - const slugs = stripLocaleFromSlug(pageSlug); - const page = source.getPage(slugs, locale); - if (!page) notFound(); - - return new ImageResponse( - , - { - width: 1200, - height: 630, - }, - ); -} - -export function generateStaticParams() { - return source.getPages().map((page) => ({ - slug: getPageImage(page).segments, - })); -} diff --git a/docs/src/lib/i18n.ts b/docs/src/lib/i18n.ts index 350f748..4b5526f 100644 --- a/docs/src/lib/i18n.ts +++ b/docs/src/lib/i18n.ts @@ -3,6 +3,7 @@ import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ languages: ['en', 'zh'], defaultLanguage: 'en', + // Hide locale prefix for default language (en) so English content appears at root hideLocale: 'default-locale', parser: 'dir' }); @@ -12,7 +13,7 @@ export type Locale = (typeof i18n.languages)[number]; const localeSet = new Set(i18n.languages); export function isLocale(value?: string): value is Locale { - return Boolean(value && localeSet.has(value)); + return Boolean(value && localeSet.has(value as Locale)); } export function resolveLocaleFromSlug(slug?: string[]): Locale { diff --git a/docs/src/middleware.ts b/docs/src/middleware.ts index c8ee8ba..dda3835 100644 --- a/docs/src/middleware.ts +++ b/docs/src/middleware.ts @@ -4,5 +4,6 @@ import { i18n } from '@/lib/i18n'; export default createI18nMiddleware(i18n); export const config = { - matcher: ['/((?!api|_next/static|_next/image|favicon.ico|og|llms\.mdx|llms-full\.txt).*)'], + // Note: Middleware doesn't run in static export, but kept for development + matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], };