Skip to content

Commit 32305f4

Browse files
committed
Fix error when accessing some not found pages
Fix GITBOOK-OPEN-1XDB Fix GITBOOK-OPEN-1XDC Fix GITBOOK-OPEN-1X07 Fix GITBOOK-OPEN-1XCJ Fix GITBOOK-OPEN-1X86
1 parent 65cc4af commit 32305f4

File tree

9 files changed

+117
-30
lines changed

9 files changed

+117
-30
lines changed

.changeset/thirty-berries-bow.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gitbook': patch
3+
---
4+
5+
Fix error when accessing some not found pages.

packages/gitbook/src/app/(site)/(content)/[[...pathname]]/not-found.tsx

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
1+
import Link from 'next/link';
2+
13
import { TrackPageViewEvent } from '@/components/Insights';
24
import { getSpaceLanguage, t } from '@/intl/server';
5+
import { languages } from '@/intl/translations';
36
import { getSiteData, getSpaceContentData } from '@/lib/api';
7+
import { checkIsFromMiddleware } from '@/lib/pages';
48
import { getSiteContentPointer } from '@/lib/pointer';
59
import { tcls } from '@/lib/tailwind';
610

711
export default async function NotFound() {
12+
const fromMiddleware = await checkIsFromMiddleware();
13+
if (!fromMiddleware) {
14+
return (
15+
<div className="flex flex-1 flex-row items-center justify-center py-9 h-screen">
16+
<div className="max-w-80">
17+
<h2 className="text-2xl font-semibold mb-2">Not found</h2>
18+
<p className="text-base mb-4">This page could not be found</p>
19+
<Link href="/" className="text-blue-500 hover:text-blue-700 underline">
20+
Go back to home
21+
</Link>
22+
</div>
23+
</div>
24+
);
25+
}
826
const pointer = await getSiteContentPointer();
927
const [{ space }, { customization }] = await Promise.all([
1028
getSpaceContentData(pointer, pointer.siteShareKey),
1129
getSiteData(pointer),
1230
]);
13-
1431
const language = getSpaceLanguage(customization);
1532

1633
return (

packages/gitbook/src/app/(site)/(content)/[[...pathname]]/page.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { CustomizationHeaderPreset, CustomizationThemeMode } from '@gitbook/api';
22
import { Metadata, Viewport } from 'next';
3+
import { headers } from 'next/headers';
34
import { notFound, redirect } from 'next/navigation';
45
import React from 'react';
56

67
import { PageAside } from '@/components/PageAside';
78
import { PageBody, PageCover } from '@/components/PageBody';
89
import { PageHrefContext, getAbsoluteHref, getPageHref } from '@/lib/links';
9-
import { getPagePath, resolveFirstDocument } from '@/lib/pages';
10+
import { checkIsFromMiddleware, getPagePath, resolveFirstDocument } from '@/lib/pages';
1011
import { ContentRefContext } from '@/lib/references';
1112
import { isSpaceIndexable, isPageIndexable } from '@/lib/seo';
1213
import { getContentTitle } from '@/lib/utils';
@@ -24,6 +25,8 @@ export default async function Page(props: {
2425
params: Promise<PagePathParams>;
2526
searchParams: Promise<{ fallback?: string }>;
2627
}) {
28+
await ensureIsFromMiddleware();
29+
2730
const { params: rawParams, searchParams: rawSearchParams } = props;
2831

2932
const params = await rawParams;
@@ -127,6 +130,7 @@ export async function generateViewport({
127130
}: {
128131
params: Promise<PagePathParams>;
129132
}): Promise<Viewport> {
133+
await ensureIsFromMiddleware();
130134
const { customization } = await fetchPageData(await params);
131135
return {
132136
colorScheme: customization.themes.toggeable
@@ -144,6 +148,8 @@ export async function generateMetadata({
144148
params: Promise<PagePathParams>;
145149
searchParams: Promise<{ fallback?: string }>;
146150
}): Promise<Metadata> {
151+
await ensureIsFromMiddleware();
152+
147153
const { space, pages, page, customization, site, ancestors } = await getPageDataWithFallback({
148154
pagePathParams: await params,
149155
searchParams: await searchParams,
@@ -206,3 +212,15 @@ async function getPageDataWithFallback(args: {
206212
page,
207213
};
208214
}
215+
216+
/**
217+
* Returns a page not found if the request is not from the middleware.
218+
* Some pages can be
219+
*/
220+
async function ensureIsFromMiddleware() {
221+
// To check if the request is from the middleware, we check if the x-gitbook-token is set in the headers.
222+
const fromMiddleware = await checkIsFromMiddleware();
223+
if (!fromMiddleware) {
224+
notFound();
225+
}
226+
}

packages/gitbook/src/app/(site)/(content)/layout.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { assetsDomain } from '@/lib/assets';
1414
import { buildVersion } from '@/lib/build';
1515
import { getContentSecurityPolicyNonce } from '@/lib/csp';
1616
import { getAbsoluteHref, getBaseUrl } from '@/lib/links';
17+
import { checkIsFromMiddleware } from '@/lib/pages';
1718
import { isSpaceIndexable } from '@/lib/seo';
1819
import { getContentTitle } from '@/lib/utils';
1920

@@ -29,6 +30,10 @@ export const dynamic = 'force-dynamic';
2930
*/
3031
export default async function ContentLayout(props: { children: React.ReactNode }) {
3132
const { children } = props;
33+
const fromMiddleware = await checkIsFromMiddleware();
34+
if (!fromMiddleware) {
35+
return props.children;
36+
}
3237

3338
const nonce = await getContentSecurityPolicyNonce();
3439
const {
@@ -106,6 +111,11 @@ export default async function ContentLayout(props: { children: React.ReactNode }
106111
}
107112

108113
export async function generateViewport(): Promise<Viewport> {
114+
const fromMiddleware = await checkIsFromMiddleware();
115+
if (!fromMiddleware) {
116+
return {};
117+
}
118+
109119
const { customization } = await fetchContentData();
110120
return {
111121
colorScheme: customization.themes.toggeable
@@ -117,6 +127,14 @@ export async function generateViewport(): Promise<Viewport> {
117127
}
118128

119129
export async function generateMetadata(): Promise<Metadata> {
130+
const fromMiddleware = await checkIsFromMiddleware();
131+
if (!fromMiddleware) {
132+
return {
133+
title: 'Not found',
134+
robots: 'noindex, nofollow',
135+
};
136+
}
137+
120138
const { space, site, customization } = await fetchContentData();
121139
const customIcon = 'icon' in customization.favicon ? customization.favicon.icon : null;
122140

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
import { CustomizationRootLayout } from '@/components/RootLayout';
22
import { getSiteData } from '@/lib/api';
3+
import { checkIsFromMiddleware } from '@/lib/pages';
34
import { getSiteContentPointer } from '@/lib/pointer';
45

56
/**
67
* Layout to be used for the site root. It fetches the customization data for the site
78
* and initializes the CustomizationRootLayout with it.
89
*/
910
export default async function SiteRootLayout(props: { children: React.ReactNode }) {
10-
const { children } = props;
11+
const fromMiddleware = await checkIsFromMiddleware();
12+
if (!fromMiddleware) {
13+
return (
14+
<html lang="en">
15+
<body className="font-[sans-serif]">
16+
<main>{props.children}</main>
17+
</body>
18+
</html>
19+
);
20+
}
1121

1222
const pointer = await getSiteContentPointer();
1323
const { customization } = await getSiteData(pointer);
1424

1525
return (
16-
<CustomizationRootLayout customization={customization}>{children}</CustomizationRootLayout>
26+
<CustomizationRootLayout customization={customization}>
27+
{props.children}
28+
</CustomizationRootLayout>
1729
);
1830
}

packages/gitbook/src/components/DocumentView/Embed.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ReactDOM from 'react-dom';
55

66
import { Card } from '@/components/primitives';
77
import { getEmbedByUrlInSpace, getEmbedByUrl } from '@/lib/api';
8+
import { getContentSecurityPolicyNonce } from '@/lib/csp';
89
import { tcls } from '@/lib/tailwind';
910

1011
import { BlockProps } from './Block';
@@ -13,8 +14,7 @@ import { IntegrationBlock } from './Integration';
1314

1415
export async function Embed(props: BlockProps<gitbookAPI.DocumentBlockEmbed>) {
1516
const { block, context, ...otherProps } = props;
16-
const headersList = await headers();
17-
const nonce = headersList.get('x-nonce') || undefined;
17+
const nonce = await getContentSecurityPolicyNonce();
1818

1919
ReactDOM.preload('https://cdn.iframe.ly/embed.js', { as: 'script', nonce });
2020

packages/gitbook/src/lib/csp.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { SpaceIntegrationScript } from '@gitbook/api';
22
import { merge } from 'content-security-policy-merger';
33
import { headers } from 'next/headers';
4+
import { assert } from 'ts-essentials';
45

56
import { assetsDomain } from './assets';
67
import { filterOutNullable } from './typescript';
8+
79
/**
810
* Get the current nonce for the current request.
911
*/
1012
export async function getContentSecurityPolicyNonce(): Promise<string> {
1113
const headersList = await headers();
1214
const nonce = headersList.get('x-nonce');
13-
if (!nonce) {
14-
throw new Error('No nonce found in headers');
15-
}
16-
15+
assert(nonce, 'x-nonce should be set in the headers by the middleware');
1716
return nonce;
1817
}
1918

packages/gitbook/src/lib/pages.ts

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
RevisionPageGroup,
66
RevisionPageType,
77
} from '@gitbook/api';
8+
import { headers } from 'next/headers';
89

910
export type AncestorRevisionPage = RevisionPageDocument | RevisionPageGroup;
1011

@@ -184,3 +185,13 @@ function flattenPages(
184185

185186
return result;
186187
}
188+
189+
/**
190+
* Returns a page not found if the request is not from the middleware.
191+
* Some pages can be
192+
*/
193+
export async function checkIsFromMiddleware() {
194+
const headerList = await headers();
195+
// To check if the request is from the middleware, we check if the x-gitbook-token is set in the headers.
196+
return Boolean(headerList.get('x-gitbook-token'));
197+
}

packages/gitbook/src/lib/pointer.ts

+27-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { headers } from 'next/headers';
2+
import { assert } from 'ts-essentials';
23

34
import { SiteContentPointer, SpaceContentPointer } from './api';
45

@@ -7,28 +8,34 @@ import { SiteContentPointer, SpaceContentPointer } from './api';
78
*/
89
export async function getSiteContentPointer(): Promise<SiteContentPointer> {
910
const headersList = await headers();
11+
1012
const spaceId = headersList.get('x-gitbook-content-space');
13+
assert(spaceId, 'x-gitbook-content-space should be set in the headers by the middleware');
14+
1115
const siteId = headersList.get('x-gitbook-content-site');
16+
assert(siteId, 'x-gitbook-content-site should be set in the headers by the middleware');
17+
1218
const organizationId = headersList.get('x-gitbook-content-organization');
13-
const siteSpaceId = headersList.get('x-gitbook-content-site-space');
14-
const siteSectionId = headersList.get('x-gitbook-content-site-section');
15-
const siteShareKey = headersList.get('x-gitbook-content-site-share-key');
19+
assert(
20+
organizationId,
21+
'x-gitbook-content-organization should be set in the headers by the middleware',
22+
);
1623

17-
if (!spaceId || !siteId || !organizationId) {
18-
throw new Error(
19-
'getSiteContentPointer is called outside the scope of a request processed by the middleware',
20-
);
21-
}
24+
const siteSectionId = headersList.get('x-gitbook-content-site-section') ?? undefined;
25+
const siteSpaceId = headersList.get('x-gitbook-content-site-space') ?? undefined;
26+
const siteShareKey = headersList.get('x-gitbook-content-site-share-key') ?? undefined;
27+
const revisionId = headersList.get('x-gitbook-content-revision') ?? undefined;
28+
const changeRequestId = headersList.get('x-gitbook-content-changerequest') ?? undefined;
2229

2330
const pointer: SiteContentPointer = {
2431
siteId,
2532
spaceId,
26-
siteSectionId: siteSectionId ?? undefined,
27-
siteSpaceId: siteSpaceId ?? undefined,
28-
siteShareKey: siteShareKey ?? undefined,
2933
organizationId,
30-
revisionId: headersList.get('x-gitbook-content-revision') ?? undefined,
31-
changeRequestId: headersList.get('x-gitbook-content-changerequest') ?? undefined,
34+
siteSectionId,
35+
siteSpaceId,
36+
siteShareKey,
37+
revisionId,
38+
changeRequestId,
3239
};
3340

3441
return pointer;
@@ -40,17 +47,17 @@ export async function getSiteContentPointer(): Promise<SiteContentPointer> {
4047
*/
4148
export async function getSpacePointer(): Promise<SpaceContentPointer> {
4249
const headersList = await headers();
50+
4351
const spaceId = headersList.get('x-gitbook-content-space');
44-
if (!spaceId) {
45-
throw new Error(
46-
'getSpacePointer is called outside the scope of a request processed by the middleware',
47-
);
48-
}
52+
assert(spaceId, 'x-gitbook-content-space should be set in the headers by the middleware');
53+
54+
const revisionId = headersList.get('x-gitbook-content-revision') ?? undefined;
55+
const changeRequestId = headersList.get('x-gitbook-content-changerequest') ?? undefined;
4956

5057
const pointer: SpaceContentPointer = {
5158
spaceId,
52-
revisionId: headersList.get('x-gitbook-content-revision') ?? undefined,
53-
changeRequestId: headersList.get('x-gitbook-content-changerequest') ?? undefined,
59+
revisionId,
60+
changeRequestId,
5461
};
5562

5663
return pointer;

0 commit comments

Comments
 (0)