Files
stanza/apps/web/src/routes/docs.$.tsx
T

103 lines
4.0 KiB
TypeScript

import { createFileRoute, notFound } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { renderServerComponent } from "@tanstack/react-start/rsc";
import { DocsBody } from "fumadocs-ui/layouts/docs/page";
import { createRelativeLink } from "fumadocs-ui/mdx";
import { RootProvider } from "fumadocs-ui/provider/tanstack";
import { DocsActions } from "@/components/docs/docs-actions";
import { DocsSidebar } from "@/components/docs/docs-sidebar";
import { DocsToc } from "@/components/docs/docs-toc";
import { getMDXComponents } from "@/components/mdx";
import { buildHead, getTechArticleJsonLd, SITE_URL } from "@/lib/seo";
import { source } from "@/lib/source";
import { getDocMeta } from "@/server/docs-meta.functions";
// Render the docs layout (sidebar + article + TOC) as one RSC fragment.
// Fumadocs' `pageTree.name` and `TOCItemType.title` are typed as `ReactNode`,
// so they can't ride along in JSON loader data — but inside an RSC fragment
// they flow through React Flight natively. Client components in the tree ship
// as `'use client'` references and hydrate normally.
const getDocLayout = createServerFn({ method: "GET" })
.inputValidator((slugs: string[]) => slugs)
.handler(async ({ data: slugs }) => {
const page = source.getPage(slugs);
if (!page) throw notFound();
const MDX = page.data.body;
const markdownPath = `${page.url}.md`;
const pageUrl = `${SITE_URL.replace(/\/$/, "")}${page.url}`;
return await renderServerComponent(
<RootProvider theme={{ enabled: false }} search={{ enabled: false }}>
<div className="mx-auto max-w-7xl px-4 sm:px-6">
<div className="md:flex md:gap-8">
<DocsSidebar tree={source.getPageTree()} />
<div className="min-w-0 flex-1">
<div className="xl:flex xl:gap-8">
<article className="min-w-0 flex-1 pt-2 pb-8 md:pt-8">
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 flex-1">
<h1 className="scroll-mt-16 text-3xl font-medium tracking-tight text-balance">
{page.data.title}
</h1>
{page.data.description && (
<p className="mt-2 text-base leading-normal text-pretty text-muted-foreground">
{page.data.description}
</p>
)}
</div>
<DocsActions
markdownPath={markdownPath}
pageUrl={pageUrl}
className="mt-1 shrink-0"
/>
</div>
<DocsBody className="mt-8">
<MDX
components={getMDXComponents({
a: createRelativeLink(source, page),
})}
/>
</DocsBody>
</article>
<DocsToc toc={page.data.toc} />
</div>
</div>
</div>
</div>
</RootProvider>,
);
});
export const Route = createFileRoute("/docs/$")({
component: Page,
loader: async ({ params }) => {
const slugs = params["_splat"]?.split("/").filter(Boolean) ?? [];
const [meta, Content] = await Promise.all([
getDocMeta({ data: slugs }),
getDocLayout({ data: slugs }),
]);
return { ...meta, Content };
},
head: ({ loaderData }) => {
const path = loaderData?.url ?? "/docs";
const title = loaderData?.title;
const description = loaderData?.description;
return buildHead({
title,
description,
path,
ogImage: loaderData ? `/og${path}.webp` : undefined,
markdownPath: loaderData ? `${path}.md` : undefined,
jsonLd:
loaderData && title
? [getTechArticleJsonLd({ title, description: description ?? "", path })]
: undefined,
});
},
});
function Page() {
const { Content } = Route.useLoaderData();
return <>{Content}</>;
}