mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 17:43:24 +00:00
fixes
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
import { SitePageSkeleton } from '@/components/SitePage';
|
||||
|
||||
export default function Loading() {
|
||||
return <SitePageSkeleton />;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getVisitorAuthClaims, getVisitorAuthClaimsFromToken } from '@/lib/adaptive';
|
||||
import { cache } from '@/lib/cache';
|
||||
import { type SiteURLData, fetchSiteContextByURLLookup, getBaseContext } from '@/lib/context';
|
||||
import { getDynamicCustomizationSettings } from '@/lib/customization';
|
||||
import type { SiteAPIToken } from '@gitbook/api';
|
||||
@@ -26,56 +27,81 @@ export type RouteParams = RouteLayoutParams & {
|
||||
* Get the static context when rendering statically a site.
|
||||
*/
|
||||
export async function getStaticSiteContext(params: RouteLayoutParams) {
|
||||
const siteURL = getSiteURLFromParams(params);
|
||||
const siteURLData = getSiteURLDataFromParams(params);
|
||||
|
||||
// For static routes, we check the expiration of the JWT token
|
||||
// as the route might be revalidated after expiration
|
||||
const decoded = jwtDecode<SiteAPIToken & { exp: number }>(siteURLData.apiToken);
|
||||
if (decoded.exp && decoded.exp < Date.now() / 1000 + 120) {
|
||||
forbidden();
|
||||
}
|
||||
|
||||
const context = await fetchSiteContextByURLLookup(
|
||||
getBaseContext({
|
||||
siteURL,
|
||||
siteURLData,
|
||||
urlMode: getModeFromParams(params.mode),
|
||||
}),
|
||||
siteURLData
|
||||
);
|
||||
|
||||
return {
|
||||
context,
|
||||
visitorAuthClaims: getVisitorAuthClaimsFromToken(decoded),
|
||||
};
|
||||
return fetchStaticSiteContext(params.mode, params.siteURL, params.siteData);
|
||||
}
|
||||
|
||||
// Deduped per request so the layout, page and their metadata/viewport generators don't each
|
||||
// re-parse the site structure. Keyed on the primitive route params — the pagePath differs between
|
||||
// layout and page, so it is intentionally excluded to keep a single cache entry per request.
|
||||
const fetchStaticSiteContext = cache(
|
||||
async (mode: string, encodedSiteURL: string, encodedSiteData: string) => {
|
||||
const params: RouteLayoutParams = {
|
||||
mode,
|
||||
siteURL: encodedSiteURL,
|
||||
siteData: encodedSiteData,
|
||||
};
|
||||
const siteURL = getSiteURLFromParams(params);
|
||||
const siteURLData = getSiteURLDataFromParams(params);
|
||||
|
||||
// For static routes, we check the expiration of the JWT token
|
||||
// as the route might be revalidated after expiration
|
||||
const decoded = jwtDecode<SiteAPIToken & { exp: number }>(siteURLData.apiToken);
|
||||
if (decoded.exp && decoded.exp < Date.now() / 1000 + 120) {
|
||||
forbidden();
|
||||
}
|
||||
|
||||
const context = await fetchSiteContextByURLLookup(
|
||||
getBaseContext({
|
||||
siteURL,
|
||||
siteURLData,
|
||||
urlMode: getModeFromParams(params.mode),
|
||||
}),
|
||||
siteURLData
|
||||
);
|
||||
|
||||
return {
|
||||
context,
|
||||
visitorAuthClaims: getVisitorAuthClaimsFromToken(decoded),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the site context when rendering dynamically.
|
||||
* The context will depend on the request.
|
||||
*/
|
||||
export async function getDynamicSiteContext(params: RouteLayoutParams) {
|
||||
const siteURL = getSiteURLFromParams(params);
|
||||
const siteURLData = getSiteURLDataFromParams(params);
|
||||
|
||||
const context = await fetchSiteContextByURLLookup(
|
||||
getBaseContext({
|
||||
siteURL,
|
||||
siteURLData,
|
||||
urlMode: getModeFromParams(params.mode),
|
||||
}),
|
||||
siteURLData
|
||||
);
|
||||
|
||||
context.customization = await getDynamicCustomizationSettings(context.customization);
|
||||
|
||||
return {
|
||||
context,
|
||||
visitorAuthClaims: getVisitorAuthClaims(siteURLData),
|
||||
};
|
||||
return fetchDynamicSiteContext(params.mode, params.siteURL, params.siteData);
|
||||
}
|
||||
|
||||
const fetchDynamicSiteContext = cache(
|
||||
async (mode: string, encodedSiteURL: string, encodedSiteData: string) => {
|
||||
const params: RouteLayoutParams = {
|
||||
mode,
|
||||
siteURL: encodedSiteURL,
|
||||
siteData: encodedSiteData,
|
||||
};
|
||||
const siteURL = getSiteURLFromParams(params);
|
||||
const siteURLData = getSiteURLDataFromParams(params);
|
||||
|
||||
const context = await fetchSiteContextByURLLookup(
|
||||
getBaseContext({
|
||||
siteURL,
|
||||
siteURLData,
|
||||
urlMode: getModeFromParams(params.mode),
|
||||
}),
|
||||
siteURLData
|
||||
);
|
||||
|
||||
context.customization = await getDynamicCustomizationSettings(context.customization);
|
||||
|
||||
return {
|
||||
context,
|
||||
visitorAuthClaims: getVisitorAuthClaims(siteURLData),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the decoded page path from the params.
|
||||
*/
|
||||
|
||||
@@ -29,15 +29,10 @@ interface PageCoverImageProps {
|
||||
|
||||
export function PageCoverImage(props: PageCoverImageProps) {
|
||||
const { imgs, y, height, mask } = props;
|
||||
const { containerRef, objectPositionY, isLoading } = useCoverPosition(imgs, y);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="h-full w-full overflow-hidden" ref={containerRef}>
|
||||
<div className="h-full w-full animate-pulse bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-900" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// The image is always rendered server-side (reserving space via aspect-ratio) so it stays
|
||||
// discoverable by the preload scanner as the LCP element; the client probe only refines
|
||||
// `objectPositionY` once real dimensions are known.
|
||||
const { containerRef, objectPositionY } = useCoverPosition(imgs, y);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-hidden" ref={containerRef} style={{ height }}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { CustomizationDefaultThemeMode } from '@gitbook/api';
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import Script from 'next/script';
|
||||
import React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
@@ -39,12 +40,6 @@ export async function SiteLayout(props: {
|
||||
ReactDOM.preconnect(GITBOOK_ASSETS_URL);
|
||||
}
|
||||
|
||||
scripts.forEach(({ script }) => {
|
||||
ReactDOM.preload(script, {
|
||||
as: 'script',
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<SiteLayoutClientContexts
|
||||
contextId={context.contextId}
|
||||
@@ -76,7 +71,9 @@ export async function SiteLayout(props: {
|
||||
|
||||
<LoadIntegrations />
|
||||
{scripts.length > 0
|
||||
? scripts.map(({ script }) => <script key={script} async src={script} />)
|
||||
? scripts.map(({ script }) => (
|
||||
<Script key={script} src={script} strategy="afterInteractive" />
|
||||
))
|
||||
: null}
|
||||
|
||||
{scripts.some((script) => script.cookies) || customization.privacyPolicy.url ? (
|
||||
|
||||
@@ -159,7 +159,7 @@ export async function generateSitePageViewport(context: GitBookSiteContext): Pro
|
||||
}
|
||||
|
||||
export async function generateSitePageMetadata(props: SitePageProps): Promise<Metadata> {
|
||||
const { context, pageTarget, pageMetaLinks } = await getPageDataWithFallback({
|
||||
const { context, pageTarget } = await getPageDataWithFallback({
|
||||
context: props.context,
|
||||
pagePathParams: props.pageParams,
|
||||
});
|
||||
@@ -174,6 +174,8 @@ export async function generateSitePageMetadata(props: SitePageProps): Promise<Me
|
||||
const { page, ancestors } = pageTarget;
|
||||
const { customization, revision, linker, imageResizer } = context;
|
||||
|
||||
const pageMetaLinks = await resolvePageMetaLinks(context, page.id);
|
||||
|
||||
const canonical = (
|
||||
pageMetaLinks?.canonical
|
||||
? new URL(
|
||||
@@ -246,7 +248,7 @@ export async function generateSitePageMetadata(props: SitePageProps): Promise<Me
|
||||
* Fetches all the data required to render the site page.
|
||||
*/
|
||||
export async function getSitePageData(props: SitePageProps) {
|
||||
const { context, pageTarget, pageMetaLinks } = await getPageDataWithFallback({
|
||||
const { context, pageTarget } = await getPageDataWithFallback({
|
||||
context: props.context,
|
||||
pagePathParams: props.pageParams,
|
||||
});
|
||||
@@ -287,7 +289,12 @@ export async function getSitePageData(props: SitePageProps) {
|
||||
|
||||
const withSections = Boolean(visibleSections && visibleSections.list.length > 0);
|
||||
|
||||
const document = await getPageDocument(context, page);
|
||||
// The page document and its meta links are independent; resolve them concurrently to
|
||||
// avoid stacking two round trips in front of the LCP content.
|
||||
const [document, pageMetaLinks] = await Promise.all([
|
||||
getPageDocument(context, page),
|
||||
resolvePageMetaLinks(context, page.id),
|
||||
]);
|
||||
const iconStyle = getCustomizationIconStyle(customization);
|
||||
const iconSources = await getInlineIconSources(
|
||||
getContentInlineIconSourceRequests({
|
||||
@@ -319,9 +326,6 @@ async function getPageDataWithFallback(args: {
|
||||
}) {
|
||||
const { context: baseContext, pagePathParams } = args;
|
||||
const { context, pageTarget } = await fetchPageData(baseContext, pagePathParams);
|
||||
const pageMetaLinks = await (pageTarget?.page
|
||||
? resolvePageMetaLinks(context, pageTarget.page.id)
|
||||
: null);
|
||||
|
||||
return {
|
||||
context: {
|
||||
@@ -329,7 +333,6 @@ async function getPageDataWithFallback(args: {
|
||||
page: pageTarget?.page,
|
||||
},
|
||||
pageTarget,
|
||||
pageMetaLinks,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user