Skip to content

Commit d07aed1

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 d07aed1

File tree

9 files changed

+116
-30
lines changed

9 files changed

+116
-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

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
import { TrackPageViewEvent } from '@/components/Insights';
22
import { getSpaceLanguage, t } from '@/intl/server';
3+
import { languages } from '@/intl/translations';
34
import { getSiteData, getSpaceContentData } from '@/lib/api';
5+
import { checkIsFromMiddleware } from '@/lib/pages';
46
import { getSiteContentPointer } from '@/lib/pointer';
57
import { tcls } from '@/lib/tailwind';
8+
import Link from 'next/link';
69

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

1632
return (

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

+19-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import React from 'react';
66
import { PageAside } from '@/components/PageAside';
77
import { PageBody, PageCover } from '@/components/PageBody';
88
import { PageHrefContext, getAbsoluteHref, getPageHref } from '@/lib/links';
9-
import { getPagePath, resolveFirstDocument } from '@/lib/pages';
9+
import { checkIsFromMiddleware, getPagePath, resolveFirstDocument } from '@/lib/pages';
1010
import { ContentRefContext } from '@/lib/references';
1111
import { isSpaceIndexable, isPageIndexable } from '@/lib/seo';
1212
import { getContentTitle } from '@/lib/utils';
1313

1414
import { PageClientLayout } from './PageClientLayout';
1515
import { PagePathParams, fetchPageData, getPathnameParam, normalizePathname } from '../../fetch';
16+
import { headers } from 'next/headers';
1617

1718
export const runtime = 'edge';
1819
export const dynamic = 'force-dynamic';
@@ -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
@@ -20,6 +20,7 @@ import { getContentTitle } from '@/lib/utils';
2020
import { ClientContexts } from './ClientContexts';
2121
import { RocketLoaderDetector } from './RocketLoaderDetector';
2222
import { fetchContentData } from '../fetch';
23+
import { checkIsFromMiddleware } from '@/lib/pages';
2324

2425
export const runtime = 'edge';
2526
export const dynamic = 'force-dynamic';
@@ -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
@@ -10,11 +10,11 @@ import { tcls } from '@/lib/tailwind';
1010
import { BlockProps } from './Block';
1111
import { Caption } from './Caption';
1212
import { IntegrationBlock } from './Integration';
13+
import { getContentSecurityPolicyNonce } from '@/lib/csp';
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
@@ -4,16 +4,15 @@ import { headers } from 'next/headers';
44

55
import { assetsDomain } from './assets';
66
import { filterOutNullable } from './typescript';
7+
import { assert } from 'ts-essentials';
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,34 +1,41 @@
11
import { headers } from 'next/headers';
22

33
import { SiteContentPointer, SpaceContentPointer } from './api';
4+
import { assert } from 'ts-essentials';
45

56
/**
67
* Get the current site content pointer from the headers
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)