move to prefetch

This commit is contained in:
Nicolas Dorseuil
2025-06-14 12:01:02 +02:00
parent 47c80991d3
commit de19ada4fa
6 changed files with 151 additions and 154 deletions
@@ -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';
@@ -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<SiteStaticLayoutProps>) {
const startedAt = cachedDate();
console.info(`SiteStaticLayout: Starting to render static site layout at ${startedAt}`);
const { context, visitorAuthClaims } = await getStaticSiteContext(await params);
return (
-148
View File
@@ -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<JSONDocument | null>;
prefetchedRef: Promise<Map<ContentRef, Promise<ResolvedContentRef | null>>>;
}
/**
* 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<ContentRef, Promise<ResolvedContentRef | null>>();
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());
@@ -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<JSONDocument | null>;
prefetchedRef: Promise<Map<ContentRef, Promise<ResolvedContentRef | null>>>;
}
/**
* 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<ContentRef, Promise<ResolvedContentRef | null>>();
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,
};
});
@@ -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';
@@ -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';