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:
@@ -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);
|
||||
|
||||
@@ -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
46
docs/scripts/post-export.js
Executable 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!');
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
@@ -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],
|
||||
})),
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
}));
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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).*)'],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user