@@ -11,28 +11,47 @@ import {
1111import { docsMetadataTitle } from "@/lib/docs-metadata-title" ;
1212import { loadDocsNavTreeData } from "@/lib/fetch-nav" ;
1313import { navTreeToBreadcrumbs } from "@/lib/nav-tree-to-breadcrumbs" ;
14- import DocsPageContent from "../docs-page-content " ;
14+ import DocsPageContent from "../DocsPageContent " ;
1515
16- export const dynamic = "force-static" ;
16+ interface DocsRouteProps {
17+ params : Promise < { path ?: string [ ] } > ;
18+ }
19+
20+ // Disable runtime fallback routing so unknown docs paths become 404s.
1721export const dynamicParams = false ;
1822
19- interface DocsRouteProps {
20- params : Promise < { path : string [ ] } > ;
23+ // normalizePathSegments converts an optional catch-all param into a concrete array.
24+ function normalizePathSegments ( path : string [ ] | undefined ) : string [ ] {
25+ return path ?? [ ] ;
26+ }
27+
28+ // toActivePageSlug maps an optional catch-all route to the docs slug used by loaders.
29+ function toActivePageSlug ( path : string [ ] ) : string {
30+ return path . length === 0 ? "index" : path . join ( "/" ) ;
2131}
2232
33+ // isErrorWithCode narrows unknown errors so filesystem codes can be checked safely.
34+ function isErrorWithCode ( err : unknown ) : err is Error & { code : unknown } {
35+ return err instanceof Error && typeof err === "object" && "code" in err ;
36+ }
37+
38+ // loadDocsRouteData loads all data needed to render a docs page and its metadata.
2339async function loadDocsRouteData ( path : string [ ] ) : Promise < {
2440 navTreeData : NavTreeNode [ ] ;
2541 docsPageData : DocsPageData ;
2642 breadcrumbs : Breadcrumb [ ] ;
2743} > {
28- const activePageSlug = path . join ( "/" ) ;
44+ const activePageSlug = toActivePageSlug ( path ) ;
2945 const navTreeData = await loadDocsNavTreeData ( DOCS_DIRECTORY , activePageSlug ) ;
3046
3147 let docsPageData : DocsPageData ;
3248 try {
3349 docsPageData = await loadDocsPage ( DOCS_DIRECTORY , activePageSlug ) ;
34- } catch {
35- notFound ( ) ;
50+ } catch ( err ) {
51+ if ( isErrorWithCode ( err ) && err . code === "ENOENT" ) {
52+ notFound ( ) ;
53+ }
54+ throw err ;
3655 }
3756
3857 const breadcrumbs = navTreeToBreadcrumbs (
@@ -41,34 +60,43 @@ async function loadDocsRouteData(path: string[]): Promise<{
4160 navTreeData ,
4261 activePageSlug ,
4362 ) ;
63+
4464 return { navTreeData, docsPageData, breadcrumbs } ;
4565}
4666
67+ // generateStaticParams pre-renders the docs index and every nested docs slug.
4768export async function generateStaticParams ( ) : Promise <
4869 Array < { path : string [ ] } >
4970> {
5071 const docsPageSlugs = await loadAllDocsPageSlugs ( DOCS_DIRECTORY ) ;
51- return docsPageSlugs
72+ const docsPagePaths = docsPageSlugs
5273 . filter ( ( slug ) => slug !== "index" )
5374 . map ( ( slug ) => ( { path : slug . split ( "/" ) } ) ) ;
75+
76+ return [ { path : [ ] } , ...docsPagePaths ] ;
5477}
5578
79+ // generateMetadata builds SEO metadata from the resolved docs page and breadcrumbs.
5680export async function generateMetadata ( {
5781 params,
5882} : DocsRouteProps ) : Promise < Metadata > {
5983 const { path } = await params ;
60- const { docsPageData, breadcrumbs } = await loadDocsRouteData ( path ) ;
84+ const { docsPageData, breadcrumbs } = await loadDocsRouteData (
85+ normalizePathSegments ( path ) ,
86+ ) ;
6187
6288 return {
6389 title : docsMetadataTitle ( breadcrumbs ) ,
6490 description : docsPageData . description ,
6591 } ;
6692}
6793
94+ // DocsPage renders both /docs and /docs/* routes via a single optional catch-all route.
6895export default async function DocsPage ( { params } : DocsRouteProps ) {
6996 const { path } = await params ;
70- const { navTreeData, docsPageData, breadcrumbs } =
71- await loadDocsRouteData ( path ) ;
97+ const { navTreeData, docsPageData, breadcrumbs } = await loadDocsRouteData (
98+ normalizePathSegments ( path ) ,
99+ ) ;
72100
73101 return (
74102 < DocsPageContent
0 commit comments