diff --git a/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/[pagePath]/page.tsx b/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/[pagePath]/page.tsx index e7c058945..45bbdc976 100644 --- a/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/[pagePath]/page.tsx +++ b/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/[pagePath]/page.tsx @@ -4,7 +4,7 @@ import { generateSitePageViewport, } from '@/components/SitePage'; import type { RouteParams } from '@v2/app/utils'; -import { getPrefetchedDataFromLayoutParams } from '@v2/lib/data/memoize'; +import { getPrefetchedDataFromLayoutParams } from '@v2/lib/data/prefetch'; import type { Metadata, Viewport } from 'next'; diff --git a/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/layout.tsx b/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/layout.tsx index 0558a96fa..396d333d6 100644 --- a/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/layout.tsx +++ b/packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/layout.tsx @@ -5,7 +5,7 @@ import { generateSiteLayoutViewport, } from '@/components/SiteLayout'; import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils'; -import { cachedDate, getPrefetchedDataFromLayoutParams } from '@v2/lib/data/memoize'; +import { getPrefetchedDataFromLayoutParams } from '@v2/lib/data/prefetch'; import { GITBOOK_DISABLE_TRACKING } from '@v2/lib/env'; interface SiteStaticLayoutProps { @@ -16,8 +16,6 @@ export default async function SiteStaticLayout({ params, children, }: React.PropsWithChildren) { - const startedAt = cachedDate(); - console.info(`SiteStaticLayout: Starting to render static site layout at ${startedAt}`); const { context, visitorAuthClaims } = await getStaticSiteContext(await params); return ( diff --git a/packages/gitbook-v2/src/lib/data/memoize.ts b/packages/gitbook-v2/src/lib/data/memoize.ts index 9e974b040..5110d015f 100644 --- a/packages/gitbook-v2/src/lib/data/memoize.ts +++ b/packages/gitbook-v2/src/lib/data/memoize.ts @@ -1,18 +1,4 @@ -import { type PagePathParams, fetchPageData } from '@/components/SitePage/fetch'; -import type { VisitorAuthClaims } from '@/lib/adaptive'; -import type { AncestorRevisionPage } from '@/lib/pages'; -import { type ResolvedContentRef, resolveContentRef } from '@/lib/references'; -import type { ContentRef, JSONDocument, RevisionPageDocument } from '@gitbook/api'; -import { - type RouteLayoutParams, - type RouteParams, - getIcon, - getPagePathFromParams, - getStaticSiteContext, -} from '@v2/app/utils'; import { cache } from 'react'; -import type { GitBookSiteContext } from '../context'; -import { getPageDocument } from './pages'; // This is used to create a context specific to the current request. // This version works both in cloudflare and in vercel. @@ -104,137 +90,3 @@ function deepSortValue(value: unknown): unknown { return value; } - -export interface PrefetchedLayoutData { - staticSiteContext: Promise<{ - context: GitBookSiteContext; - visitorAuthClaims: VisitorAuthClaims; - }>; - icons: Promise< - { - url: string; - type: string; - media: string; - }[] - >; -} - -export interface PrefetchedPageData { - pageData: Promise<{ - context: GitBookSiteContext & { - page?: RevisionPageDocument; - }; - pageTarget?: { - page: RevisionPageDocument; - ancestors: AncestorRevisionPage[]; - }; - }>; - document: Promise; - prefetchedRef: Promise>>; -} - -/** - * Fetches the page data matching the requested pathname and fallback to root page when page is not found. - */ -async function getPageDataWithFallback(args: { - context: GitBookSiteContext; - pagePathParams: PagePathParams; -}) { - const { context: baseContext, pagePathParams } = args; - const { context, pageTarget } = await fetchPageData(baseContext, pagePathParams); - - return { - context: { - ...context, - page: pageTarget?.page, - }, - pageTarget, - }; -} - -export const getPrefetchedDataFromLayoutParams = cache( - (params: RouteLayoutParams): PrefetchedLayoutData => { - const staticSiteContext = getStaticSiteContext(params); - const icons = Promise.all([ - staticSiteContext.then(({ context }) => getIcon(context, 'light')), - staticSiteContext.then(({ context }) => getIcon(context, 'dark')), - ]).then((urls) => [ - { - url: urls[0], - type: 'image/png', - media: '(prefers-color-scheme: light)', - }, - { - url: urls[1], - type: 'image/png', - media: '(prefers-color-scheme: dark)', - }, - ]); - - return { - staticSiteContext, - icons, - }; - } -); - -export const prefetchedDocumentRef = ( - document: JSONDocument | null, - context: GitBookSiteContext -) => { - const fetched = new Map>(); - if (!document) return fetched; - - const traverseNodes = (nodes: any[]): void => { - for (const node of nodes) { - // We try prefetching as many references as possible. - if (node.data?.ref) { - fetched.set(node.data.ref, resolveContentRef(node.data.ref, context)); - } - if (node.nodes && Array.isArray(node.nodes)) { - traverseNodes(node.nodes); - } - if (node.fragments && Array.isArray(node.fragments)) { - traverseNodes(node.fragments); - } - } - }; - - if (document.nodes && Array.isArray(document.nodes)) { - traverseNodes(document.nodes); - } - console.log('Prefetched document references:', fetched.size); - return fetched; -}; - -export const getPrefetchedDataFromPageParams = cache((params: RouteParams): PrefetchedPageData => { - const { staticSiteContext } = getPrefetchedDataFromLayoutParams(params); - const pathname = getPagePathFromParams(params); - const pageData = staticSiteContext.then(({ context }) => - getPageDataWithFallback({ - context, - pagePathParams: { - pathname, - }, - }) - ); - const document = pageData.then(({ context, pageTarget }) => { - if (!pageTarget?.page) { - return null; - } - return getPageDocument(context.dataFetcher, context.space, pageTarget?.page); - }); - const prefetchedRef = Promise.all([staticSiteContext, document]).then( - ([{ context }, document]) => { - // Prefetch the references in the document - return prefetchedDocumentRef(document, context); - } - ); - return { - pageData, - document, - prefetchedRef, - }; -}); - -export const cachedDate = cache(() => Date.now()); diff --git a/packages/gitbook-v2/src/lib/data/prefetch.ts b/packages/gitbook-v2/src/lib/data/prefetch.ts new file mode 100644 index 000000000..5f9dd152a --- /dev/null +++ b/packages/gitbook-v2/src/lib/data/prefetch.ts @@ -0,0 +1,147 @@ +import { type PagePathParams, fetchPageData } from '@/components/SitePage'; +import type { VisitorAuthClaims } from '@/lib/adaptive'; +import type { AncestorRevisionPage } from '@/lib/pages'; +import { type ResolvedContentRef, resolveContentRef } from '@/lib/references'; +import type { ContentRef, JSONDocument, RevisionPageDocument } from '@gitbook/api'; +import { + type RouteLayoutParams, + type RouteParams, + getIcon, + getPagePathFromParams, + getStaticSiteContext, +} from '@v2/app/utils'; +import { cache } from 'react'; +import type { GitBookSiteContext } from '../context'; +import { getPageDocument } from './pages'; + +export interface PrefetchedLayoutData { + staticSiteContext: Promise<{ + context: GitBookSiteContext; + visitorAuthClaims: VisitorAuthClaims; + }>; + icons: Promise< + { + url: string; + type: string; + media: string; + }[] + >; +} + +export interface PrefetchedPageData { + pageData: Promise<{ + context: GitBookSiteContext & { + page?: RevisionPageDocument; + }; + pageTarget?: { + page: RevisionPageDocument; + ancestors: AncestorRevisionPage[]; + }; + }>; + document: Promise; + prefetchedRef: Promise>>; +} + +/** + * Fetches the page data matching the requested pathname and fallback to root page when page is not found. + */ +async function getPageDataWithFallback(args: { + context: GitBookSiteContext; + pagePathParams: PagePathParams; +}) { + const { context: baseContext, pagePathParams } = args; + const { context, pageTarget } = await fetchPageData(baseContext, pagePathParams); + + return { + context: { + ...context, + page: pageTarget?.page, + }, + pageTarget, + }; +} + +export const getPrefetchedDataFromLayoutParams = cache( + (params: RouteLayoutParams): PrefetchedLayoutData => { + const staticSiteContext = getStaticSiteContext(params); + const icons = Promise.all([ + staticSiteContext.then(({ context }) => getIcon(context, 'light')), + staticSiteContext.then(({ context }) => getIcon(context, 'dark')), + ]).then((urls) => [ + { + url: urls[0], + type: 'image/png', + media: '(prefers-color-scheme: light)', + }, + { + url: urls[1], + type: 'image/png', + media: '(prefers-color-scheme: dark)', + }, + ]); + + return { + staticSiteContext, + icons, + }; + } +); + +export const prefetchedDocumentRef = ( + document: JSONDocument | null, + context: GitBookSiteContext +) => { + const fetched = new Map>(); + if (!document) return fetched; + + const traverseNodes = (nodes: any[]): void => { + for (const node of nodes) { + // We try prefetching as many references as possible. + if (node.data?.ref) { + fetched.set(node.data.ref, resolveContentRef(node.data.ref, context)); + } + if (node.nodes && Array.isArray(node.nodes)) { + traverseNodes(node.nodes); + } + if (node.fragments && Array.isArray(node.fragments)) { + traverseNodes(node.fragments); + } + } + }; + + if (document.nodes && Array.isArray(document.nodes)) { + traverseNodes(document.nodes); + } + console.log('Prefetched document references:', fetched.size); + return fetched; +}; + +export const getPrefetchedDataFromPageParams = cache((params: RouteParams): PrefetchedPageData => { + const { staticSiteContext } = getPrefetchedDataFromLayoutParams(params); + const pathname = getPagePathFromParams(params); + const pageData = staticSiteContext.then(({ context }) => + getPageDataWithFallback({ + context, + pagePathParams: { + pathname, + }, + }) + ); + const document = pageData.then(({ context, pageTarget }) => { + if (!pageTarget?.page) { + return null; + } + return getPageDocument(context, pageTarget?.page); + }); + const prefetchedRef = Promise.all([staticSiteContext, document]).then( + ([{ context }, document]) => { + // Prefetch the references in the document + return prefetchedDocumentRef(document, context); + } + ); + return { + pageData, + document, + prefetchedRef, + }; +}); diff --git a/packages/gitbook/src/components/SiteLayout/SiteLayout.tsx b/packages/gitbook/src/components/SiteLayout/SiteLayout.tsx index 3e9e389b9..36ac720cc 100644 --- a/packages/gitbook/src/components/SiteLayout/SiteLayout.tsx +++ b/packages/gitbook/src/components/SiteLayout/SiteLayout.tsx @@ -13,7 +13,7 @@ import { buildVersion } from '@/lib/build'; import { isSiteIndexable } from '@/lib/seo'; import type { VisitorAuthClaims } from '@/lib/adaptive'; -import type { PrefetchedLayoutData } from '@v2/lib/data/memoize'; +import type { PrefetchedLayoutData } from '@v2/lib/data/prefetch'; import { GITBOOK_API_PUBLIC_URL, GITBOOK_ASSETS_URL, GITBOOK_ICONS_URL } from '@v2/lib/env'; import { ClientContexts } from './ClientContexts'; import { RocketLoaderDetector } from './RocketLoaderDetector'; diff --git a/packages/gitbook/src/components/SitePage/SitePage.tsx b/packages/gitbook/src/components/SitePage/SitePage.tsx index fa094db87..9b4f2c4a4 100644 --- a/packages/gitbook/src/components/SitePage/SitePage.tsx +++ b/packages/gitbook/src/components/SitePage/SitePage.tsx @@ -10,7 +10,7 @@ import { getPagePath } from '@/lib/pages'; import { isPageIndexable, isSiteIndexable } from '@/lib/seo'; import type { RouteParams } from '@v2/app/utils'; -import { getPrefetchedDataFromPageParams } from '@v2/lib/data/memoize'; +import { getPrefetchedDataFromPageParams } from '@v2/lib/data/prefetch'; import { getResizedImageURL } from '@v2/lib/images'; import { PageContextProvider } from '../PageContext'; import { PageClientLayout } from './PageClientLayout';