mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
revert some changes
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Reduce the JavaScript and CSS loaded on published site pages: the search index and its UI now load only when search is opened, the admin toolbar and OpenAPI/ContentKit styles are no longer shipped to every visitor, and the client bundle targets modern browsers.
|
||||
Reduce the JavaScript and CSS loaded on published site pages: the search index and its UI now load only when search is opened, and the admin toolbar and OpenAPI/ContentKit styles are no longer shipped to every visitor.
|
||||
|
||||
@@ -37,16 +37,6 @@ const nextConfig = {
|
||||
optimisticClientCache: false,
|
||||
// Disable splitting the RSC in like 5 chunks
|
||||
prefetchInlining: true,
|
||||
|
||||
// Tree-shake barrel imports from these packages so only the used entrypoints ship
|
||||
// in the client bundle (notably `motion`, which is otherwise pulled in wholesale).
|
||||
optimizePackageImports: [
|
||||
'motion',
|
||||
'@gitbook/icons',
|
||||
'react-aria',
|
||||
'react-aria-components',
|
||||
'react-stately',
|
||||
],
|
||||
},
|
||||
|
||||
env: {
|
||||
|
||||
@@ -138,7 +138,9 @@
|
||||
"e2e-browserless": "bun test ./tests/",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"browserslist": ["chrome >= 93, edge >= 93, firefox >= 92, safari >= 15.4, not dead"],
|
||||
"browserslist": [
|
||||
">0.3%, chrome >= 64, edge >= 79, firefox >= 67, opera >= 51, safari >= 12 and not dead"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
|
||||
BIN
Binary file not shown.
@@ -1,5 +1,4 @@
|
||||
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';
|
||||
@@ -27,16 +26,6 @@ export type RouteParams = RouteLayoutParams & {
|
||||
* Get the static context when rendering statically a site.
|
||||
*/
|
||||
export async function getStaticSiteContext(params: RouteLayoutParams) {
|
||||
// Only the fields the context depends on — dropping pagePath so the layout, page and their
|
||||
// metadata/viewport generators all share a single cached execution per request.
|
||||
return fetchStaticSiteContext({
|
||||
mode: params.mode,
|
||||
siteURL: params.siteURL,
|
||||
siteData: params.siteData,
|
||||
});
|
||||
}
|
||||
|
||||
const fetchStaticSiteContext = cache(async (params: RouteLayoutParams) => {
|
||||
const siteURL = getSiteURLFromParams(params);
|
||||
const siteURLData = getSiteURLDataFromParams(params);
|
||||
|
||||
@@ -60,21 +49,13 @@ const fetchStaticSiteContext = cache(async (params: RouteLayoutParams) => {
|
||||
context,
|
||||
visitorAuthClaims: getVisitorAuthClaimsFromToken(decoded),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the site context when rendering dynamically.
|
||||
* The context will depend on the request.
|
||||
*/
|
||||
export async function getDynamicSiteContext(params: RouteLayoutParams) {
|
||||
return fetchDynamicSiteContext({
|
||||
mode: params.mode,
|
||||
siteURL: params.siteURL,
|
||||
siteData: params.siteData,
|
||||
});
|
||||
}
|
||||
|
||||
const fetchDynamicSiteContext = cache(async (params: RouteLayoutParams) => {
|
||||
const siteURL = getSiteURLFromParams(params);
|
||||
const siteURLData = getSiteURLDataFromParams(params);
|
||||
|
||||
@@ -93,7 +74,7 @@ const fetchDynamicSiteContext = cache(async (params: RouteLayoutParams) => {
|
||||
context,
|
||||
visitorAuthClaims: getVisitorAuthClaims(siteURLData),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the decoded page path from the params.
|
||||
|
||||
@@ -29,10 +29,15 @@ interface PageCoverImageProps {
|
||||
|
||||
export function PageCoverImage(props: PageCoverImageProps) {
|
||||
const { imgs, y, height, mask } = props;
|
||||
// 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);
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-hidden" ref={containerRef} style={{ height }}>
|
||||
|
||||
@@ -28,6 +28,7 @@ interface Images {
|
||||
export function useCoverPosition(imgs: Images, y: number) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [loadedDimensions, setLoadedDimensions] = useState<ImageSize | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(!imgs.light.size && !imgs.dark?.size);
|
||||
|
||||
const container = useResizeObserver({
|
||||
// @ts-expect-error wrong types
|
||||
@@ -43,6 +44,8 @@ export function useCoverPosition(imgs: Images, y: number) {
|
||||
return; // Already have dimensions
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
// Load the original image (using src, not srcSet) to get true dimensions
|
||||
// Use dark image if available, otherwise fall back to light
|
||||
const imageToLoad = imgs.dark || imgs.light;
|
||||
@@ -52,6 +55,11 @@ export function useCoverPosition(imgs: Images, y: number) {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
});
|
||||
setIsLoading(false);
|
||||
};
|
||||
img.onerror = () => {
|
||||
// If image fails to load, use a fallback
|
||||
setIsLoading(false);
|
||||
};
|
||||
img.src = imageToLoad.src;
|
||||
}, [imgs.light, imgs.dark]);
|
||||
@@ -96,5 +104,6 @@ export function useCoverPosition(imgs: Images, y: number) {
|
||||
return {
|
||||
containerRef,
|
||||
objectPositionY,
|
||||
isLoading: !imageDimensions || isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,10 +20,7 @@ interface RawIndexPage {
|
||||
icon?: string;
|
||||
emoji?: string;
|
||||
description?: string;
|
||||
/** Inlined breadcrumbs (version 1 responses). */
|
||||
breadcrumbs?: Breadcrumb[];
|
||||
/** Indices into the response-level `crumbs` table (version 2 responses). */
|
||||
breadcrumbRefs?: number[];
|
||||
}
|
||||
|
||||
/** FlexSearch-compatible document type — satisfies DocumentData via explicit index signature */
|
||||
@@ -123,21 +120,7 @@ async function getOrBuildIndexes(indexURL: string): Promise<Map<string, Document
|
||||
throw new Error(`Failed to fetch search index: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: { version: number; pages: RawIndexPage[]; crumbs?: Breadcrumb[] } =
|
||||
await response.json();
|
||||
|
||||
// Version 2 dedupes breadcrumbs into a shared `crumbs` table; resolve the per-page
|
||||
// references back to inline breadcrumbs so the rest of the code is version-agnostic.
|
||||
if (data.crumbs) {
|
||||
const crumbs = data.crumbs;
|
||||
for (const page of data.pages) {
|
||||
if (page.breadcrumbRefs) {
|
||||
page.breadcrumbs = page.breadcrumbRefs
|
||||
.map((index) => crumbs[index])
|
||||
.filter((crumb): crumb is Breadcrumb => crumb !== undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
const data: { version: 1; pages: RawIndexPage[] } = await response.json();
|
||||
|
||||
// Group pages by their `lang` value (empty string for pages without one)
|
||||
const pagesByLang = new Map<string, RawIndexPage[]>();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -40,6 +39,12 @@ export async function SiteLayout(props: {
|
||||
ReactDOM.preconnect(GITBOOK_ASSETS_URL);
|
||||
}
|
||||
|
||||
scripts.forEach(({ script }) => {
|
||||
ReactDOM.preload(script, {
|
||||
as: 'script',
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<SiteLayoutClientContexts
|
||||
contextId={context.contextId}
|
||||
@@ -71,9 +76,7 @@ export async function SiteLayout(props: {
|
||||
|
||||
<LoadIntegrations />
|
||||
{scripts.length > 0
|
||||
? scripts.map(({ script }) => (
|
||||
<Script key={script} src={script} strategy="afterInteractive" />
|
||||
))
|
||||
? scripts.map(({ script }) => <script key={script} async src={script} />)
|
||||
: 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 } = await getPageDataWithFallback({
|
||||
const { context, pageTarget, pageMetaLinks } = await getPageDataWithFallback({
|
||||
context: props.context,
|
||||
pagePathParams: props.pageParams,
|
||||
});
|
||||
@@ -174,8 +174,6 @@ 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(
|
||||
@@ -248,7 +246,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 } = await getPageDataWithFallback({
|
||||
const { context, pageTarget, pageMetaLinks } = await getPageDataWithFallback({
|
||||
context: props.context,
|
||||
pagePathParams: props.pageParams,
|
||||
});
|
||||
@@ -289,12 +287,7 @@ export async function getSitePageData(props: SitePageProps) {
|
||||
|
||||
const withSections = Boolean(visibleSections && visibleSections.list.length > 0);
|
||||
|
||||
// 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 document = await getPageDocument(context, page);
|
||||
const iconStyle = getCustomizationIconStyle(customization);
|
||||
const iconSources = await getInlineIconSources(
|
||||
getContentInlineIconSourceRequests({
|
||||
@@ -326,6 +319,9 @@ 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: {
|
||||
@@ -333,6 +329,7 @@ async function getPageDataWithFallback(args: {
|
||||
page: pageTarget?.page,
|
||||
},
|
||||
pageTarget,
|
||||
pageMetaLinks,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Icon, type IconName } from '@gitbook/icons';
|
||||
import leven from 'leven';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { preload } from 'react-dom';
|
||||
import { useAI } from '../AI';
|
||||
import { PreservePageLayout } from '../PageBody/PreservePageLayout';
|
||||
import { useSetSearchState } from '../Search';
|
||||
@@ -42,11 +41,6 @@ export function SitePageNotFound() {
|
||||
const setSearchState = useSetSearchState();
|
||||
const { assistants } = useAI();
|
||||
|
||||
// getRelatedPages (below) fetches the site index on mount; preload it so the request starts
|
||||
// before hydration. Scoped to the 404 page — the common way to hit a broken link cold —
|
||||
// rather than the previous every-page preload.
|
||||
preload(siteIndexURL, { as: 'fetch', type: 'application/json' });
|
||||
|
||||
// Show the assistant input when a non-search assistant is available (i.e. not just ask-AI).
|
||||
const assistant = assistants.find((candidate) => candidate.mode !== 'search') ?? null;
|
||||
const inputLabel = tString(language, assistant ? 'search_or_ask' : 'search');
|
||||
@@ -69,7 +63,7 @@ export function SitePageNotFound() {
|
||||
}
|
||||
|
||||
// Otherwise, rank the site's pages against the path that 404'd. We reuse the search index
|
||||
// (preloaded above and CDN-cached), so this adds no extra origin request — see getRelatedPages.
|
||||
// (already preloaded and CDN-cached), so this adds no origin request — see getRelatedPages.
|
||||
let active = true;
|
||||
getRelatedPages(siteIndexURL, pathname ?? '', siteSpaceId).then(
|
||||
(pages) => {
|
||||
@@ -249,8 +243,9 @@ type IndexPage = {
|
||||
* Return the pages whose path is closest to the one that 404'd.
|
||||
*
|
||||
* Rather than asking the server (which would mean an extra request per 404), this reuses the
|
||||
* CDN-cached search index served at `~gitbook/site-index`. The ranking is a lighter, client-side
|
||||
* cousin of `getSimilarPages` (which the Markdown 404 runs server-side from the full page tree).
|
||||
* search index served at `~gitbook/site-index` — already preloaded and CDN-cached on every page —
|
||||
* so it's a cache hit, not an origin request. The ranking is a lighter, client-side cousin of
|
||||
* `getSimilarPages` (which the Markdown 404 runs server-side from the full page tree).
|
||||
*/
|
||||
async function getRelatedPages(
|
||||
indexURL: string,
|
||||
|
||||
Reference in New Issue
Block a user