From fa41af43d82d021741eaac9a2abac34eec95b1e6 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Fri, 8 May 2026 10:13:04 +0530 Subject: [PATCH] perf: cache sorted page tree and precompute nav map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tree sorting and nav computation ran on every /api/page request. Now cached after first call — getPageNav is O(1) map lookup. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/lib/source.ts | 37 +++++++++++++------ .../chronicle/src/server/entry-server.tsx | 2 +- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/chronicle/src/lib/source.ts b/packages/chronicle/src/lib/source.ts index ee1556ee..581e3e44 100644 --- a/packages/chronicle/src/lib/source.ts +++ b/packages/chronicle/src/lib/source.ts @@ -97,6 +97,8 @@ function buildSyntheticMeta(): { } let cachedSource: ReturnType | null = null; +let cachedTree: Root | null = null; +let cachedNavMap: Map | null = null; async function getSource() { if (cachedSource) return cachedSource; @@ -112,6 +114,8 @@ export { getSource as source }; export function invalidate() { cachedSource = null; + cachedTree = null; + cachedNavMap = null; } function getOrder(node: Node, orderMap: Map): number | undefined { @@ -146,8 +150,10 @@ function sortTreeByOrder(tree: Root, pages: { url: string; data: unknown }[]): R } export async function getPageTree(): Promise { + if (cachedTree) return cachedTree; const s = await getSource(); - return sortTreeByOrder(s.pageTree as Root, s.getPages()); + cachedTree = sortTreeByOrder(s.pageTree as Root, s.getPages()); + return cachedTree; } export async function getPages() { @@ -186,12 +192,10 @@ function titleFromUrl(url: string): string { .join(' '); } -export async function getPageNav(slug: string[], tree?: Root): Promise { - const resolvedTree = tree ?? (await getPageTree()); - const pages = flattenTree(resolvedTree.children); - const url = slug.length === 0 ? '/' : `/${slug.join('/')}`; - const i = pages.findIndex(p => p.url === url); - if (i < 0) return { prev: null, next: null }; +async function getNavMap(): Promise> { + if (cachedNavMap) return cachedNavMap; + const tree = await getPageTree(); + const pages = flattenTree(tree.children); const toLink = (p: (typeof pages)[number]): PageNavLink => ({ url: p.url, title: @@ -199,10 +203,21 @@ export async function getPageNav(slug: string[], tree?: Root): Promise ? p.name : titleFromUrl(p.url) }); - return { - prev: i > 0 ? toLink(pages[i - 1]) : null, - next: i < pages.length - 1 ? toLink(pages[i + 1]) : null - }; + const navMap = new Map(); + for (let i = 0; i < pages.length; i++) { + navMap.set(pages[i].url, { + prev: i > 0 ? toLink(pages[i - 1]) : null, + next: i < pages.length - 1 ? toLink(pages[i + 1]) : null + }); + } + cachedNavMap = navMap; + return cachedNavMap; +} + +export async function getPageNav(slug: string[]): Promise { + const navMap = await getNavMap(); + const url = slug.length === 0 ? '/' : `/${slug.join('/')}`; + return navMap.get(url) ?? { prev: null, next: null }; } export function extractFrontmatter(page: { data: unknown }, fallbackTitle?: string): Frontmatter { diff --git a/packages/chronicle/src/server/entry-server.tsx b/packages/chronicle/src/server/entry-server.tsx index b313ac9b..b4c039ed 100644 --- a/packages/chronicle/src/server/entry-server.tsx +++ b/packages/chronicle/src/server/entry-server.tsx @@ -45,7 +45,7 @@ export default { getPageTree(), route.type === RouteType.DocsPage ? getPage(route.slug) : Promise.resolve(null), ]); - const nav = page ? await getPageNav(pageSlug, tree) : { prev: null, next: null }; + const nav = page ? await getPageNav(pageSlug) : { prev: null, next: null }; const relativePath = page ? getRelativePath(page) : null; const originalPath = page ? getOriginalPath(page) : null;