perf: optimize docs static generation cost (#12108)

## Summary
- Switch the docs catch-all route to full static generation and prebuild
both latest and versioned docs paths.
- Prebuild OG images for all docs pages, make sitemap and LLM export
static, and remove nondeterministic sitemap timestamps.
- Reduce build-time overhead by memoizing docs sidebar trees and
skipping processed markdown generation for versioned docs.

## Testing
- `pnpm nx run @zitadel/docs:build`
- `pnpm nx run @zitadel/docs:lint`
- `pnpm nx run @zitadel/docs:check-types`
- Verified the prerender manifest contains 8,816 prerendered routes,
3,293 versioned docs routes, 4,406 OG routes, and zero revalidating docs
routes.
This commit is contained in:
Florian Forster
2026-04-27 15:34:00 +00:00
committed by GitHub
parent f96a9c54ac
commit 24d0f34f39
6 changed files with 94 additions and 89 deletions
+47 -55
View File
@@ -1,69 +1,61 @@
import { source, versionSource } from '@/lib/source';
import { getAllDocPages, source, versionSource } from '@/lib/source';
import { ReactNode } from 'react';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { baseOptions } from '@/lib/layout.shared';
import { buildCustomTree } from '@/lib/custom-tree';
import rawVersions from '@/content/versions.json';
const versions = rawVersions as any[];
import { VersionSelector } from '@/components/version-selector';
type DocVersion = { param: string };
type PageTree = typeof source.pageTree;
const versions = rawVersions as DocVersion[];
const versionParams = new Set(versions.map((version) => version.param));
const labelsMap = new Map<string, string>();
for (const page of getAllDocPages()) {
if (page.data.sidebar_label) {
labelsMap.set(page.url, page.data.sidebar_label);
}
}
const latestTree = buildCustomTree(source.pageTree, { labels: labelsMap });
const versionTreeCache = new Map<string, PageTree>();
function getVersionTree(currentVersion: string): PageTree {
const cached = versionTreeCache.get(currentVersion);
if (cached) return cached;
const children = (versionSource.pageTree as any).children || [];
const versionFolder = children.find((node: any) => {
if (node.type !== 'folder') return false;
if (node.index?.url && node.index.url.includes(`/${currentVersion}`)) return true;
return Boolean(node.children?.[0]?.url?.includes(`/${currentVersion}`));
});
const tree = versionFolder
? buildCustomTree(
{
name: versionFolder.name,
children: versionFolder.children,
} as any,
{
stripPrefix: `/${currentVersion}`,
labels: labelsMap,
},
)
: buildCustomTree(versionSource.pageTree, { labels: labelsMap });
versionTreeCache.set(currentVersion, tree);
return tree;
}
export default async function Layout(props: { children: ReactNode; params: Promise<{ slug?: string[] }> }) {
const params = await props.params;
const slug = params.slug || [];
// Determine current version and tail path
// If first slug segment matches a version param (e.g. v4.10), that's the version.
// Otherwise it's latest.
const versionParam = slug[0] && versions.find(v => v.param === slug[0]) ? slug[0] : undefined;
const currentVersion = versionParam || 'latest';
const allPages = [...source.getPages(), ...versionSource.getPages()];
const labelsMap = new Map<string, string>();
allPages.forEach(p => {
if (p.data.sidebar_label) {
labelsMap.set(p.url, p.data.sidebar_label);
}
});
let tree: typeof source.pageTree;
if (currentVersion !== 'latest') {
// Hoist the version folder to root to flatten sidebar
// versionSource.pageTree -> [ v4.10 folder, v4.9 folder ... ]
// We want the children of the specific version folder.
const children = (versionSource.pageTree as any).children || [];
const versionFolder = children.find((node: any) => {
if (node.type !== 'folder') return false;
// Check if the folder's index page belongs to this version
if (node.index?.url && node.index.url.includes(`/${currentVersion}`)) return true;
// Fallback: check first child if index missing
if (node.children?.[0]?.url?.includes(`/${currentVersion}`)) return true;
return false;
});
if (versionFolder) {
// Hoist children
const hoistedTree = {
name: versionFolder.name,
children: versionFolder.children,
} as any;
// Apply custom sidebar structure (from local sidebar-data.ts)
// We strip the prefix '/v4.10' so the lookup matches generic keys 'guides/...'
// The logs showed the URLs start with /[version] not /docs/[version]
const prefix = currentVersion === 'latest' ? '/docs' : `/${currentVersion}`;
tree = buildCustomTree(hoistedTree, {
stripPrefix: prefix,
labels: labelsMap
});
} else {
tree = buildCustomTree(versionSource.pageTree, { labels: labelsMap });
}
} else {
tree = buildCustomTree(source.pageTree, { labels: labelsMap });
}
const currentVersion = slug[0] && versionParams.has(slug[0]) ? slug[0] : 'latest';
const tree = currentVersion === 'latest' ? latestTree : getVersionTree(currentVersion);
return (
<DocsLayout
+2 -2
View File
@@ -1,4 +1,4 @@
import { getPageImage, getPage, source } from '@/lib/source';
import { generateAllDocParams, getPageImage, getPage, source } from '@/lib/source';
import {
DocsBody,
DocsPage,
@@ -43,7 +43,7 @@ export const revalidate = false;
export const dynamic = 'force-static';
export async function generateStaticParams() {
return source.generateParams();
return generateAllDocParams();
}
export async function generateMetadata(