fix(docs): resolve TypeScript build errors (#144)

* fix(docs): resolve TypeScript build errors and search configuration

Fix invalid LayoutProps type in [lang]/(docs)/layout.tsx to use proper Next.js 16 async layout component signature. Add type assertion in i18n.ts isLocale function to satisfy strict type checking. Configure search to use English indexing since Orama doesn't support Chinese.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat(docs): configure static export following Fumadocs guidelines

- Enable Next.js static export with output: 'export'
- Configure static search using staticGET and revalidate: false
- Update search UI to use type: 'static' for client-side search
- Add localeMap to support both English and Chinese locales
- Remove rewrites (incompatible with static export)
- Remove route handlers that conflict with static export:
  - /llms.mdx route (file extension conflicts)
  - /llms-full.txt route
  - /og/docs OG image generation
- Add trailingSlash: true for better route handling

Build successfully generates 9 static pages (2.7MB total).
Search indexes are exported as static files for client-side use.

Reference: https://www.fumadocs.dev/docs/deploying/static

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix(docs): resolve 404 issues in static export

- Change hideLocale from 'default-locale' to 'never' for static export
  compatibility (middleware doesn't work in static builds)
- Add public/index.html to redirect root path to /en/
- Update middleware matcher to remove deleted route exclusions
- All pages now accessible with language prefix (/en/, /zh/)

This fixes the 404 issue where middleware-based rewrites don't work
in static exports. Now all URLs explicitly include the language code.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat(docs): serve English content at root path without redirect

- Restore hideLocale to 'default-locale' for cleaner URLs
- Add post-export script to copy /en/ content to root directory
- English pages now accessible at both / and /en/
- Chinese pages remain at /zh/
- Remove redirect page in favor of direct content serving
- Add "type": "module" to package.json for ES modules

This provides a better user experience by serving English content
directly at the root path (/, /cookies/, /faq/) without requiring
a redirect, while maintaining /en/ for explicit access.

Output size: 3.2MB (includes both root and /en/ copies)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Nexmoe
2026-01-17 16:31:24 +08:00
committed by GitHub
parent 1f821b67dd
commit 129899ed61
11 changed files with 84 additions and 83 deletions

View File

@@ -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);

View File

@@ -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",

46
docs/scripts/post-export.js Executable file
View File

@@ -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!');

View File

@@ -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 (

View File

@@ -24,5 +24,16 @@ export default async function Layout({
const { lang } = await params;
const locale = isLocale(lang) ? lang : i18n.defaultLanguage;
return <RootProvider i18n={provider(locale)}>{children}</RootProvider>;
return (
<RootProvider
i18n={provider(locale)}
search={{
options: {
type: 'static',
},
}}
>
{children}
</RootProvider>
);
}

View File

@@ -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' },
},
});

View File

@@ -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'));
}

View File

@@ -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],
})),
);
}

View File

@@ -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(
<DefaultImage title={page.data.title} description={page.data.description} site="VidBee" />,
{
width: 1200,
height: 630,
},
);
}
export function generateStaticParams() {
return source.getPages().map((page) => ({
slug: getPageImage(page).segments,
}));
}

View File

@@ -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 {

View File

@@ -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).*)'],
};