From cf526fb8ff7b47a9c209592dab607313c42d050a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 21 Mar 2025 09:53:07 +0100 Subject: [PATCH 1/9] Rename toPathInContent to toPathInSpace --- .../src/lib/images/createImageResizer.ts | 2 +- packages/gitbook-v2/src/lib/links.test.ts | 12 ++++++------ packages/gitbook-v2/src/lib/links.ts | 14 +++++++------- .../src/components/Header/CurrentContentIcon.tsx | 4 ++-- .../gitbook/src/components/Header/HeaderLogo.tsx | 2 +- packages/gitbook/src/components/PDF/PDFPage.tsx | 2 +- .../gitbook/src/components/PageAside/PageAside.tsx | 2 +- .../src/components/SiteLayout/SiteLayout.tsx | 6 ++---- .../gitbook/src/components/SitePage/SitePage.tsx | 4 ++-- .../src/components/SpaceLayout/SpaceLayout.tsx | 2 +- packages/gitbook/src/routes/ogimage.tsx | 4 +--- packages/gitbook/src/routes/robots.ts | 2 +- 12 files changed, 26 insertions(+), 30 deletions(-) diff --git a/packages/gitbook-v2/src/lib/images/createImageResizer.ts b/packages/gitbook-v2/src/lib/images/createImageResizer.ts index 8e56ebf22f..52cfaf68d9 100644 --- a/packages/gitbook-v2/src/lib/images/createImageResizer.ts +++ b/packages/gitbook-v2/src/lib/images/createImageResizer.ts @@ -62,7 +62,7 @@ export function createImageResizer({ url: urlInput, }); - const url = linker.toAbsoluteURL(linker.toPathInContent('/~gitbook/image')); + const url = linker.toAbsoluteURL(linker.toPathInSpace('/~gitbook/image')); const searchParams = new URLSearchParams(); searchParams.set('url', getImageAPIUrl(urlInput)); diff --git a/packages/gitbook-v2/src/lib/links.test.ts b/packages/gitbook-v2/src/lib/links.test.ts index d4937b2f28..2633dee4dd 100644 --- a/packages/gitbook-v2/src/lib/links.test.ts +++ b/packages/gitbook-v2/src/lib/links.test.ts @@ -13,13 +13,13 @@ const variantInSection = createLinker({ describe('toPathInContent', () => { it('should return the correct path', () => { - expect(root.toPathInContent('some/path')).toBe('/some/path'); - expect(variantInSection.toPathInContent('some/path')).toBe('/section/variant/some/path'); + expect(root.toPathInSpace('some/path')).toBe('/some/path'); + expect(variantInSection.toPathInSpace('some/path')).toBe('/section/variant/some/path'); }); it('should handle leading slash', () => { - expect(root.toPathInContent('/some/path')).toBe('/some/path'); - expect(variantInSection.toPathInContent('/some/path')).toBe('/section/variant/some/path'); + expect(root.toPathInSpace('/some/path')).toBe('/some/path'); + expect(variantInSection.toPathInSpace('/some/path')).toBe('/section/variant/some/path'); }); }); @@ -38,8 +38,8 @@ describe('appendBasePathToLinker', () => { describe('toPathInContent', () => { it('should return the correct path', () => { - expect(prefixedRoot.toPathInContent('some/path')).toBe('/section/variant/some/path'); - expect(prefixedVariantInSection.toPathInContent('some/path')).toBe( + expect(prefixedRoot.toPathInSpace('some/path')).toBe('/section/variant/some/path'); + expect(prefixedVariantInSection.toPathInSpace('some/path')).toBe( '/section/variant/base/some/path' ); }); diff --git a/packages/gitbook-v2/src/lib/links.ts b/packages/gitbook-v2/src/lib/links.ts index 185a204574..084cebff8c 100644 --- a/packages/gitbook-v2/src/lib/links.ts +++ b/packages/gitbook-v2/src/lib/links.ts @@ -9,7 +9,7 @@ import warnOnce from 'warn-once'; * * https://docs.company.com/section/variant/page * - * toPathInContent('some/path') => /section/variant/some/path + * toPathInSpace('some/path') => /section/variant/some/path * toPathForPage({ pages, page }) => /section/variant/some/path * toAbsoluteURL('some/path') => https://docs.company.com/some/path */ @@ -17,7 +17,7 @@ export interface GitBookSpaceLinker { /** * Generate an absolute path for a relative path to the current content. */ - toPathInContent(relativePath: string): string; + toPathInSpace(relativePath: string): string; /** * Generate an absolute path for a page in the current content. @@ -54,7 +54,7 @@ export function createLinker( warnOnce(!servedOn.host, 'No host provided to createLinker. It can lead to issues with links.'); const linker: GitBookSpaceLinker = { - toPathInContent(relativePath: string): string { + toPathInSpace(relativePath: string): string { return joinPaths(servedOn.pathname, relativePath); }, @@ -67,7 +67,7 @@ export function createLinker( }, toPathForPage({ pages, page, anchor }) { - return linker.toPathInContent(getPagePath(pages, page)) + (anchor ? `#${anchor}` : ''); + return linker.toPathInSpace(getPagePath(pages, page)) + (anchor ? `#${anchor}` : ''); }, toLinkForContent(url: string): string { @@ -86,8 +86,8 @@ export function appendBasePathToLinker( basePath: string ): GitBookSpaceLinker { const linkerWithPrefix: GitBookSpaceLinker = { - toPathInContent(relativePath: string): string { - return linker.toPathInContent(joinPaths(basePath, relativePath)); + toPathInSpace(relativePath: string): string { + return linker.toPathInSpace(joinPaths(basePath, relativePath)); }, toAbsoluteURL(absolutePath: string): string { @@ -96,7 +96,7 @@ export function appendBasePathToLinker( toPathForPage({ pages, page, anchor }) { return ( - linkerWithPrefix.toPathInContent(getPagePath(pages, page)) + + linkerWithPrefix.toPathInSpace(getPagePath(pages, page)) + (anchor ? `#${anchor}` : '') ); }, diff --git a/packages/gitbook/src/components/Header/CurrentContentIcon.tsx b/packages/gitbook/src/components/Header/CurrentContentIcon.tsx index 11fb6d2531..746fdd5061 100644 --- a/packages/gitbook/src/components/Header/CurrentContentIcon.tsx +++ b/packages/gitbook/src/components/Header/CurrentContentIcon.tsx @@ -42,11 +42,11 @@ export function CurrentContentIcon( } : { light: { - src: linker.toPathInContent('~gitbook/icon?size=medium&theme=light'), + src: linker.toPathInSpace('~gitbook/icon?size=medium&theme=light'), size: { width: 256, height: 256 }, }, dark: { - src: linker.toPathInContent('~gitbook/icon?size=medium&theme=dark'), + src: linker.toPathInSpace('~gitbook/icon?size=medium&theme=dark'), size: { width: 256, height: 256 }, }, } diff --git a/packages/gitbook/src/components/Header/HeaderLogo.tsx b/packages/gitbook/src/components/Header/HeaderLogo.tsx index 925730e3fb..08a1d2d10a 100644 --- a/packages/gitbook/src/components/Header/HeaderLogo.tsx +++ b/packages/gitbook/src/components/Header/HeaderLogo.tsx @@ -20,7 +20,7 @@ export async function HeaderLogo(props: HeaderLogoProps) { return ( {customization.header.logo ? ( diff --git a/packages/gitbook/src/components/PDF/PDFPage.tsx b/packages/gitbook/src/components/PDF/PDFPage.tsx index bff7330ffc..108a36e3bd 100644 --- a/packages/gitbook/src/components/PDF/PDFPage.tsx +++ b/packages/gitbook/src/components/PDF/PDFPage.tsx @@ -93,7 +93,7 @@ export async function PDFPage(props: {
+ ); const src = linker.toAbsoluteURL( - linker.toPathInContent( - `~gitbook/icon?size=medium&theme=${customization.themes.default}` - ) + linker.toPathInSpace(`~gitbook/icon?size=medium&theme=${customization.themes.default}`) ); return Icon; })(); diff --git a/packages/gitbook/src/routes/robots.ts b/packages/gitbook/src/routes/robots.ts index 1d8a3aed98..1026ba1d18 100644 --- a/packages/gitbook/src/routes/robots.ts +++ b/packages/gitbook/src/routes/robots.ts @@ -14,7 +14,7 @@ export async function serveRobotsTxt(context: GitBookSiteContext) { ...((await isSiteIndexable(context)) ? [ 'Allow: /', - `Sitemap: ${linker.toAbsoluteURL(linker.toPathInContent(isRoot ? '/sitemap.xml' : '/sitemap-pages.xml'))}`, + `Sitemap: ${linker.toAbsoluteURL(linker.toPathInSpace(isRoot ? '/sitemap.xml' : '/sitemap-pages.xml'))}`, ] : ['Disallow: /']), ]; From 6e1a70d98bf8f00821cd128d4b8bca7c13a6553a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 21 Mar 2025 11:37:29 +0100 Subject: [PATCH 2/9] Simplify and make it possible --- packages/gitbook-v2/src/app/utils.ts | 2 + .../src/app/~space/[spaceId]/pdf.ts | 4 +- packages/gitbook-v2/src/lib/context.ts | 99 +++++++------------ .../src/lib/images/createImageResizer.ts | 4 +- packages/gitbook-v2/src/lib/links.ts | 38 ++++--- packages/gitbook-v2/src/lib/server-actions.ts | 2 +- packages/gitbook-v2/src/middleware.ts | 39 ++++---- .../gitbook/src/components/PDF/PDFPage.tsx | 4 +- packages/gitbook/src/lib/v1.ts | 6 +- 9 files changed, 93 insertions(+), 105 deletions(-) diff --git a/packages/gitbook-v2/src/app/utils.ts b/packages/gitbook-v2/src/app/utils.ts index b6c4af2b48..3a71b300da 100644 --- a/packages/gitbook-v2/src/app/utils.ts +++ b/packages/gitbook-v2/src/app/utils.ts @@ -38,6 +38,7 @@ export async function getStaticSiteContext(params: RouteLayoutParams) { const context = await fetchSiteContextByURLLookup( getBaseContext({ siteURL, + siteURLData, urlMode: getModeFromParams(params.mode), }), siteURLData @@ -60,6 +61,7 @@ export async function getDynamicSiteContext(params: RouteLayoutParams) { const context = await fetchSiteContextByURLLookup( getBaseContext({ siteURL, + siteURLData, urlMode: getModeFromParams(params.mode), }), siteURLData diff --git a/packages/gitbook-v2/src/app/~space/[spaceId]/pdf.ts b/packages/gitbook-v2/src/app/~space/[spaceId]/pdf.ts index b4e3be3c8e..47feb1933e 100644 --- a/packages/gitbook-v2/src/app/~space/[spaceId]/pdf.ts +++ b/packages/gitbook-v2/src/app/~space/[spaceId]/pdf.ts @@ -20,8 +20,10 @@ export async function getSpacePDFContext( const apiToken = await getAPITokenFromMiddleware(); + const basePath = getPDFRoutePath(params); const linker = createLinker({ - pathname: getPDFRoutePath(params), + spaceBasePath: basePath, + siteBasePath: basePath, }); const dataFetcher = createDataFetcher({ apiToken: apiToken, diff --git a/packages/gitbook-v2/src/lib/context.ts b/packages/gitbook-v2/src/lib/context.ts index 72eafb2a5d..8d1b88efc1 100644 --- a/packages/gitbook-v2/src/lib/context.ts +++ b/packages/gitbook-v2/src/lib/context.ts @@ -1,7 +1,7 @@ import { getSiteStructureSections } from '@/lib/sites'; import type { ChangeRequest, - PublishedSiteContentLookup, + PublishedSiteContent, RevisionPage, RevisionPageDocument, Site, @@ -14,11 +14,10 @@ import type { Space, } from '@gitbook/api'; import { type GitBookDataFetcher, createDataFetcher, throwIfDataError } from '@v2/lib/data'; -import { redirect } from 'next/navigation'; import { assert } from 'ts-essentials'; import { GITBOOK_URL } from './env'; import { type ImageResizer, createImageResizer } from './images'; -import { type GitBookSpaceLinker, createLinker } from './links'; +import { type GitBookLinker, createLinker } from './links'; /** * Generic context when rendering content. @@ -32,7 +31,7 @@ export type GitBookBaseContext = { /** * Linker to generate links in the current space. */ - linker: GitBookSpaceLinker; + linker: GitBookLinker; /** * Image resizer to resize images. @@ -102,59 +101,33 @@ export type GitBookPageContext = (GitBookSpaceContext | GitBookSiteContext) & { }; /** - * Get the base context for a request. + * Get the base context for a request on a site. */ export function getBaseContext(input: { siteURL: URL | string; + siteURLData: PublishedSiteContent; urlMode: 'url' | 'url-host'; - apiToken?: string | null; }) { - const url = typeof input.siteURL === 'string' ? new URL(input.siteURL) : input.siteURL; - const urlMode = input.urlMode; + const { urlMode, siteURLData } = input; + const siteURL = typeof input.siteURL === 'string' ? new URL(input.siteURL) : input.siteURL; const dataFetcher = createDataFetcher({ - apiToken: input.apiToken ?? null, + apiToken: siteURLData.apiToken ?? null, }); - const linker = getLinkerForSiteURL({ - siteURL: url, - urlMode, - }); - - const imageResizer = createImageResizer({ - host: url.host, - // To ensure image resizing work for proxied sites, - // we serve images from the root of the site. - linker: linker, - }); - - return { - dataFetcher, - linker, - imageResizer, - }; -} - -/** - * Get the linker for a given site URL. - */ -export function getLinkerForSiteURL(input: { - siteURL: URL; - urlMode: 'url' | 'url-host'; -}) { - const { siteURL, urlMode } = input; - const gitbookURL = GITBOOK_URL ? new URL(GITBOOK_URL) : undefined; const linker = urlMode === 'url-host' ? createLinker({ host: siteURL.host, - pathname: siteURL.pathname, + siteBasePath: siteURLData.siteBasePath, + spaceBasePath: siteURLData.basePath, }) : createLinker({ protocol: gitbookURL?.protocol, host: gitbookURL?.host, - pathname: `/url/${siteURL.host}${siteURL.pathname}`, + siteBasePath: `/url/${siteURL.host}${siteURLData.siteBasePath}`, + spaceBasePath: `/url/${siteURL.host}${siteURLData.basePath}`, }); if (urlMode === 'url') { @@ -165,7 +138,18 @@ export function getLinkerForSiteURL(input: { }; } - return linker; + const imageResizer = createImageResizer({ + host: siteURL.host, + // To ensure image resizing work for proxied sites, + // we serve images from the root of the site. + linker: linker, + }); + + return { + dataFetcher, + linker, + imageResizer, + }; } /** @@ -173,31 +157,18 @@ export function getLinkerForSiteURL(input: { */ export async function fetchSiteContextByURLLookup( baseContext: GitBookBaseContext, - data: PublishedSiteContentLookup + data: PublishedSiteContent ): Promise { - const { dataFetcher } = baseContext; - if ('redirect' in data) { - redirect(data.redirect); - } - - return await fetchSiteContextByIds( - { - ...baseContext, - dataFetcher: dataFetcher.withToken({ - apiToken: data.apiToken, - }), - }, - { - organization: data.organization, - site: data.site, - siteSection: data.siteSection, - siteSpace: data.siteSpace, - space: data.space, - shareKey: data.shareKey, - changeRequest: data.changeRequest, - revision: data.revision, - } - ); + return await fetchSiteContextByIds(baseContext, { + organization: data.organization, + site: data.site, + siteSection: data.siteSection, + siteSpace: data.siteSpace, + space: data.space, + shareKey: data.shareKey, + changeRequest: data.changeRequest, + revision: data.revision, + }); } /** diff --git a/packages/gitbook-v2/src/lib/images/createImageResizer.ts b/packages/gitbook-v2/src/lib/images/createImageResizer.ts index 52cfaf68d9..fccfb2f30d 100644 --- a/packages/gitbook-v2/src/lib/images/createImageResizer.ts +++ b/packages/gitbook-v2/src/lib/images/createImageResizer.ts @@ -1,7 +1,7 @@ import 'server-only'; import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env'; -import type { GitBookSpaceLinker } from '../links'; +import type { GitBookLinker } from '../links'; import { type SignatureVersion, generateImageSignature } from './signatures'; import type { ImageResizer } from './types'; @@ -37,7 +37,7 @@ export function createImageResizer({ linker, }: { /** The linker to use to create URLs. */ - linker: GitBookSpaceLinker; + linker: GitBookLinker; /** The host name of the current site. */ host: string; }): ImageResizer { diff --git a/packages/gitbook-v2/src/lib/links.ts b/packages/gitbook-v2/src/lib/links.ts index 084cebff8c..59f9312d13 100644 --- a/packages/gitbook-v2/src/lib/links.ts +++ b/packages/gitbook-v2/src/lib/links.ts @@ -7,18 +7,24 @@ import warnOnce from 'warn-once'; * * URL levels: * - * https://docs.company.com/section/variant/page + * https://docs.company.com/basename/section/variant/page * - * toPathInSpace('some/path') => /section/variant/some/path - * toPathForPage({ pages, page }) => /section/variant/some/path + * toPathInSpace('some/path') => /basename/section/variant/some/path + * toPathInSite('some/path') => /basename/some/path + * toPathForPage({ pages, page }) => /basename/section/variant/some/path * toAbsoluteURL('some/path') => https://docs.company.com/some/path */ -export interface GitBookSpaceLinker { +export interface GitBookLinker { /** * Generate an absolute path for a relative path to the current content. */ toPathInSpace(relativePath: string): string; + /** + * Generate an absolute path for a relative path to the current site. + */ + toPathInSite(relativePath: string): string; + /** * Generate an absolute path for a page in the current content. * The result should NOT be passed to `toPathInContent`. @@ -48,14 +54,23 @@ export function createLinker( servedOn: { protocol?: string; host?: string; - pathname: string; + + /** The base path of the space */ + spaceBasePath: string; + + /** The base path of the site */ + siteBasePath: string; } -): GitBookSpaceLinker { +): GitBookLinker { warnOnce(!servedOn.host, 'No host provided to createLinker. It can lead to issues with links.'); - const linker: GitBookSpaceLinker = { + const linker: GitBookLinker = { toPathInSpace(relativePath: string): string { - return joinPaths(servedOn.pathname, relativePath); + return joinPaths(servedOn.spaceBasePath, relativePath); + }, + + toPathInSite(relativePath: string): string { + return joinPaths(servedOn.siteBasePath, relativePath); }, toAbsoluteURL(absolutePath: string): string { @@ -81,11 +96,8 @@ export function createLinker( /** * Append a prefix to a linker. */ -export function appendBasePathToLinker( - linker: GitBookSpaceLinker, - basePath: string -): GitBookSpaceLinker { - const linkerWithPrefix: GitBookSpaceLinker = { +export function appendBasePathToLinker(linker: GitBookLinker, basePath: string): GitBookLinker { + const linkerWithPrefix: GitBookLinker = { toPathInSpace(relativePath: string): string { return linker.toPathInSpace(joinPaths(basePath, relativePath)); }, diff --git a/packages/gitbook-v2/src/lib/server-actions.ts b/packages/gitbook-v2/src/lib/server-actions.ts index 08e6f0a91c..68f5b9f1b7 100644 --- a/packages/gitbook-v2/src/lib/server-actions.ts +++ b/packages/gitbook-v2/src/lib/server-actions.ts @@ -16,8 +16,8 @@ export async function getServerActionBaseContext() { return getBaseContext({ siteURL, + siteURLData, urlMode, - apiToken: siteURLData.apiToken, }); } diff --git a/packages/gitbook-v2/src/middleware.ts b/packages/gitbook-v2/src/middleware.ts index b7904a5783..da1b7609b5 100644 --- a/packages/gitbook-v2/src/middleware.ts +++ b/packages/gitbook-v2/src/middleware.ts @@ -14,7 +14,6 @@ import { normalizeVisitorAuthURL, } from '@/lib/visitor-token'; import { serveResizedImage } from '@/routes/image'; -import { getLinkerForSiteURL } from '@v2/lib/context'; import { DataFetcherError, getPublishedContentByURL, @@ -90,7 +89,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { }); const withAPIToken = async (apiToken: string | null) => { - const data = await throwIfDataError( + const siteURLData = await throwIfDataError( getPublishedContentByURL({ url: siteURL.toString(), visitorAuthToken: visitorToken?.token ?? null, @@ -109,21 +108,21 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { // // Handle redirects // - if ('redirect' in data) { + if ('redirect' in siteURLData) { // biome-ignore lint/suspicious/noConsole: we want to log the redirect - console.log('redirect', data.redirect); - if (data.target === 'content') { - // For content redirects, we use the linker to redirect the optimal URL - // during development and testing in 'url' mode. - const linker = getLinkerForSiteURL({ - siteURL, - urlMode: mode, - }); + console.log('redirect', siteURLData.redirect); + if (siteURLData.target === 'content') { + let contentRedirect = new URL(siteURLData.redirect, request.url); - const contentRedirect = new URL( - linker.toLinkForContent(data.redirect), - request.url - ); + // For content redirects, we redirect using the /url/:url format + // during development and testing in 'url' mode. + if (mode === 'url') { + const urlObject = new URL(siteURLData.redirect); + contentRedirect = new URL( + `/url/${urlObject.host}${urlObject.pathname}${urlObject.search}`, + request.url + ); + } // Keep the same search params as the original request // as it might contain a VA token @@ -132,10 +131,10 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { return NextResponse.redirect(contentRedirect); } - return NextResponse.redirect(data.redirect); + return NextResponse.redirect(siteURLData.redirect); } - cookies.push(...getResponseCookiesForVisitorAuth(data.siteBasePath, visitorToken)); + cookies.push(...getResponseCookiesForVisitorAuth(siteURLData.siteBasePath, visitorToken)); // // Make sure the URL is clean of any va token after a successful lookup @@ -189,9 +188,9 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { requestHeaders.set('x-forwarded-host', request.nextUrl.host); requestHeaders.set('origin', request.nextUrl.origin); - const siteURLWithoutProtocol = `${siteURL.host}${data.basePath}`; + const siteURLWithoutProtocol = `${siteURL.host}${siteURLData.basePath}`; const { pathname, routeType: routeTypeFromPathname } = encodePathInSiteContent( - data.pathname + siteURLData.pathname ); routeType = routeTypeFromPathname ?? routeType; @@ -200,7 +199,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { routeType, mode, encodeURIComponent(siteURLWithoutProtocol), - encodeURIComponent(rison.encode(data)), + encodeURIComponent(rison.encode(siteURLData)), pathname, ].join('/'); diff --git a/packages/gitbook/src/components/PDF/PDFPage.tsx b/packages/gitbook/src/components/PDF/PDFPage.tsx index 108a36e3bd..da6b5d8ede 100644 --- a/packages/gitbook/src/components/PDF/PDFPage.tsx +++ b/packages/gitbook/src/components/PDF/PDFPage.tsx @@ -11,7 +11,7 @@ import { import { Icon } from '@gitbook/icons'; import type { GitBookSiteContext, GitBookSpaceContext } from '@v2/lib/context'; import { getPageDocument } from '@v2/lib/data'; -import type { GitBookSpaceLinker } from '@v2/lib/links'; +import type { GitBookLinker } from '@v2/lib/links'; import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import * as React from 'react'; @@ -67,7 +67,7 @@ export async function PDFPage(props: { ); // Build a linker that create anchor links for the pages rendered in the PDF page. - const linker: GitBookSpaceLinker = { + const linker: GitBookLinker = { ...baseContext.linker, toPathForPage(input) { if (pages.some((p) => p.page.id === input.page.id)) { diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index 65e997e2c4..6afa5dfe6b 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -46,7 +46,8 @@ export async function getV1BaseContext(): Promise { const linker = createLinker({ host, - pathname: basePath, + spaceBasePath: basePath, + siteBasePath: basePath, }); const dataFetcher = await getDataFetcherV1(); @@ -56,7 +57,8 @@ export async function getV1BaseContext(): Promise { // In V1, we always resize at the top level of the hostname, not relative to the content. linker: createLinker({ host, - pathname: '/', + spaceBasePath: '/', + siteBasePath: '/', }), }); From ca18c7ac2c9bb62fefa2dd03ee603503195b3f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 21 Mar 2025 11:45:50 +0100 Subject: [PATCH 3/9] Simplify --- packages/gitbook-v2/src/lib/links.test.ts | 46 +++++++++-------------- packages/gitbook-v2/src/lib/links.ts | 28 -------------- packages/gitbook-v2/src/middleware.ts | 4 +- packages/gitbook/src/lib/references.tsx | 3 +- 4 files changed, 22 insertions(+), 59 deletions(-) diff --git a/packages/gitbook-v2/src/lib/links.test.ts b/packages/gitbook-v2/src/lib/links.test.ts index 2633dee4dd..1cfb23b90e 100644 --- a/packages/gitbook-v2/src/lib/links.test.ts +++ b/packages/gitbook-v2/src/lib/links.test.ts @@ -1,14 +1,22 @@ import { describe, expect, it } from 'bun:test'; -import { appendBasePathToLinker, createLinker } from './links'; +import { createLinker } from './links'; const root = createLinker({ host: 'docs.company.com', - pathname: '/', + spaceBasePath: '/', + siteBasePath: '/', }); const variantInSection = createLinker({ host: 'docs.company.com', - pathname: '/section/variant', + spaceBasePath: '/section/variant', + siteBasePath: '/', +}); + +const siteGitBookIO = createLinker({ + host: 'org.gitbook.io', + spaceBasePath: '/sitename/variant/', + siteBasePath: '/sitename/', }); describe('toPathInContent', () => { @@ -23,6 +31,13 @@ describe('toPathInContent', () => { }); }); +describe('toPathInSite', () => { + it('should return the correct path', () => { + expect(root.toPathInSite('some/path')).toBe('/some/path'); + expect(siteGitBookIO.toPathInSite('some/path')).toBe('/sitename/some/path'); + }); +}); + describe('toAbsoluteURL', () => { it('should return the correct path', () => { expect(root.toAbsoluteURL('some/path')).toBe('https://docs.company.com/some/path'); @@ -31,28 +46,3 @@ describe('toAbsoluteURL', () => { ); }); }); - -describe('appendBasePathToLinker', () => { - const prefixedRoot = appendBasePathToLinker(root, '/section/variant'); - const prefixedVariantInSection = appendBasePathToLinker(variantInSection, '/base'); - - describe('toPathInContent', () => { - it('should return the correct path', () => { - expect(prefixedRoot.toPathInSpace('some/path')).toBe('/section/variant/some/path'); - expect(prefixedVariantInSection.toPathInSpace('some/path')).toBe( - '/section/variant/base/some/path' - ); - }); - }); - - describe('toAbsoluteURL', () => { - it('should return the correct path', () => { - expect(prefixedRoot.toAbsoluteURL('some/path')).toBe( - 'https://docs.company.com/some/path' - ); - expect(prefixedVariantInSection.toAbsoluteURL('some/path')).toBe( - 'https://docs.company.com/some/path' - ); - }); - }); -}); diff --git a/packages/gitbook-v2/src/lib/links.ts b/packages/gitbook-v2/src/lib/links.ts index 59f9312d13..933e34d933 100644 --- a/packages/gitbook-v2/src/lib/links.ts +++ b/packages/gitbook-v2/src/lib/links.ts @@ -93,34 +93,6 @@ export function createLinker( return linker; } -/** - * Append a prefix to a linker. - */ -export function appendBasePathToLinker(linker: GitBookLinker, basePath: string): GitBookLinker { - const linkerWithPrefix: GitBookLinker = { - toPathInSpace(relativePath: string): string { - return linker.toPathInSpace(joinPaths(basePath, relativePath)); - }, - - toAbsoluteURL(absolutePath: string): string { - return linker.toAbsoluteURL(absolutePath); - }, - - toPathForPage({ pages, page, anchor }) { - return ( - linkerWithPrefix.toPathInSpace(getPagePath(pages, page)) + - (anchor ? `#${anchor}` : '') - ); - }, - - toLinkForContent(url: string): string { - return linker.toLinkForContent(url); - }, - }; - - return linkerWithPrefix; -} - function joinPaths(prefix: string, path: string): string { const prefixPath = prefix.endsWith('/') ? prefix : `${prefix}/`; const suffixPath = path.startsWith('/') ? path.slice(1) : path; diff --git a/packages/gitbook-v2/src/middleware.ts b/packages/gitbook-v2/src/middleware.ts index da1b7609b5..4382fee5ce 100644 --- a/packages/gitbook-v2/src/middleware.ts +++ b/packages/gitbook-v2/src/middleware.ts @@ -163,8 +163,8 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { const requestHeaders = new Headers(request.headers); requestHeaders.set(MiddlewareHeaders.RouteType, routeType); requestHeaders.set(MiddlewareHeaders.URLMode, mode); - requestHeaders.set(MiddlewareHeaders.SiteURL, `${siteURL.origin}${data.basePath}`); - requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(data)); + requestHeaders.set(MiddlewareHeaders.SiteURL, `${siteURL.origin}${siteURLData.basePath}`); + requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(siteURLData)); // Preview of customization/theme const customization = siteURL.searchParams.get('customization'); diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index caff358e3d..83b5a6f098 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -323,7 +323,8 @@ async function resolveContentRefInSpace( ); const linker = createLinker({ host: baseURL.host, - pathname: baseURL.pathname, + spaceBasePath: baseURL.pathname, + siteBasePath: baseURL.pathname, }); const resolved = await resolveContentRef( From 9a88148f0350faa80edc1a552181ccd71fd403a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 21 Mar 2025 14:36:06 +0100 Subject: [PATCH 4/9] Use next link to sections --- packages/gitbook-v2/src/lib/links.test.ts | 15 +++++++++++++++ packages/gitbook-v2/src/lib/links.ts | 12 ++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/gitbook-v2/src/lib/links.test.ts b/packages/gitbook-v2/src/lib/links.test.ts index 1cfb23b90e..cfbfcbced1 100644 --- a/packages/gitbook-v2/src/lib/links.test.ts +++ b/packages/gitbook-v2/src/lib/links.test.ts @@ -46,3 +46,18 @@ describe('toAbsoluteURL', () => { ); }); }); + +describe('toLinkForContent', () => { + it('should return the correct path', () => { + expect(root.toLinkForContent('https://docs.company.com/some/path')).toBe('/some/path'); + expect(siteGitBookIO.toLinkForContent('https://org.gitbook.io/sitename/some/path')).toBe( + '/sitename/some/path' + ); + }); + + it('should preserve an absolute URL if the site is not the same', () => { + expect(siteGitBookIO.toLinkForContent('https://org.gitbook.io/anothersite/some/path')).toBe( + 'https://org.gitbook.io/anothersite/some/path' + ); + }); +}); diff --git a/packages/gitbook-v2/src/lib/links.ts b/packages/gitbook-v2/src/lib/links.ts index 933e34d933..4ff67379df 100644 --- a/packages/gitbook-v2/src/lib/links.ts +++ b/packages/gitbook-v2/src/lib/links.ts @@ -85,8 +85,16 @@ export function createLinker( return linker.toPathInSpace(getPagePath(pages, page)) + (anchor ? `#${anchor}` : ''); }, - toLinkForContent(url: string): string { - return url; + toLinkForContent(rawURL: string): string { + const url = new URL(rawURL); + + // If the link points to a content in the same site, we return an absolute path + // instead of a full URL; it makes it possible to use router navigation + if (url.hostname === servedOn.host && url.pathname.startsWith(servedOn.siteBasePath)) { + return url.pathname; + } + + return rawURL; }, }; From 1efb826be46fc4c04ec7e25f46bccb9951325ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 21 Mar 2025 16:49:08 +0100 Subject: [PATCH 5/9] Safer --- packages/gitbook-v2/src/middleware.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gitbook-v2/src/middleware.ts b/packages/gitbook-v2/src/middleware.ts index 4382fee5ce..3ba0d98d3e 100644 --- a/packages/gitbook-v2/src/middleware.ts +++ b/packages/gitbook-v2/src/middleware.ts @@ -386,6 +386,7 @@ function encodePathInSiteContent(rawPathname: string): { case '~gitbook/icon': case 'llms.txt': case 'sitemap.xml': + case 'sitemap-pages.xml': case 'robots.txt': return { pathname }; case '~gitbook/pdf': From acd17922669e3c73c8596316237ee5495207cb3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 21 Mar 2025 16:57:26 +0100 Subject: [PATCH 6/9] Resize image at the site level --- packages/gitbook-v2/src/lib/images/createImageResizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gitbook-v2/src/lib/images/createImageResizer.ts b/packages/gitbook-v2/src/lib/images/createImageResizer.ts index fccfb2f30d..ff6813554d 100644 --- a/packages/gitbook-v2/src/lib/images/createImageResizer.ts +++ b/packages/gitbook-v2/src/lib/images/createImageResizer.ts @@ -62,7 +62,7 @@ export function createImageResizer({ url: urlInput, }); - const url = linker.toAbsoluteURL(linker.toPathInSpace('/~gitbook/image')); + const url = linker.toAbsoluteURL(linker.toPathInSite('/~gitbook/image')); const searchParams = new URLSearchParams(); searchParams.set('url', getImageAPIUrl(urlInput)); From dcc4cfe30d84bfbf7a3832b15f3aefd1bb2bebc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 21 Mar 2025 17:13:22 +0100 Subject: [PATCH 7/9] Prefetch as much as possible --- packages/gitbook/src/components/Header/Dropdown.tsx | 1 - packages/gitbook/src/components/Search/SearchAskAnswer.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/gitbook/src/components/Header/Dropdown.tsx b/packages/gitbook/src/components/Header/Dropdown.tsx index 82432ea412..2d078c3b9a 100644 --- a/packages/gitbook/src/components/Header/Dropdown.tsx +++ b/packages/gitbook/src/components/Header/Dropdown.tsx @@ -123,7 +123,6 @@ export function DropdownMenuItem( return ( Date: Fri, 21 Mar 2025 17:50:02 +0100 Subject: [PATCH 8/9] Increase duration of memory cache --- packages/gitbook-v2/next.config.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/gitbook-v2/next.config.mjs b/packages/gitbook-v2/next.config.mjs index 0361a6937d..df57b9540d 100644 --- a/packages/gitbook-v2/next.config.mjs +++ b/packages/gitbook-v2/next.config.mjs @@ -5,8 +5,17 @@ */ const nextConfig = { experimental: { + // This is needed to throw "forbidden" when the api token expired during revalidation authInterrupts: true, + + // This is needed to use 'use cache' useCache: true, + + // Content is fully static, we can cache it in the session memory cache for a long time + staleTimes: { + dynamic: 3600, // 1 hour + static: 3600, // 1 hour + }, }, env: { From 62c880cbc6a23bd06e127f10321028fc3ff39f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 21 Mar 2025 18:05:39 +0100 Subject: [PATCH 9/9] Disable it for v1 --- packages/gitbook/src/lib/v1.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index 6afa5dfe6b..9713ad58b3 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -50,6 +50,11 @@ export async function getV1BaseContext(): Promise { siteBasePath: basePath, }); + // On V1, we use hard-navigation between different spaces because of layout issues + linker.toLinkForContent = (url) => { + return url; + }; + const dataFetcher = await getDataFetcherV1(); const imageResizer = createImageResizer({