Skip to content

Commit c85d3f9

Browse files
SamyPesseCopilot
andauthored
Serve a markdown version of the page when adding a .md extension (#3055)
Co-authored-by: Copilot <[email protected]>
1 parent 4d7088b commit c85d3f9

File tree

14 files changed

+175
-65
lines changed

14 files changed

+175
-65
lines changed

packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/llms.txt/route.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/robots.txt/route.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/sitemap-pages.xml/route.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/gitbook-v2/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/sitemap.xml/route.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/gitbook-v2/src/app/sites/static/[mode]/[siteURL]/[siteData]/llms.txt/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ export async function GET(
1010
{ params }: { params: Promise<RouteLayoutParams> }
1111
) {
1212
const { context } = await getStaticSiteContext(await params);
13-
return serveLLMsTxt(context);
13+
return serveLLMsTxt(context, { withMarkdownPages: true });
1414
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { servePageMarkdown } from '@/routes/markdownPage';
2+
import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@v2/app/utils';
3+
import type { NextRequest } from 'next/server';
4+
5+
export const dynamic = 'force-static';
6+
7+
export async function GET(_request: NextRequest, { params }: { params: Promise<RouteParams> }) {
8+
const { context } = await getStaticSiteContext(await params);
9+
const pathname = getPagePathFromParams(await params);
10+
return servePageMarkdown(context, pathname);
11+
}

packages/gitbook-v2/src/lib/data/api.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-tags';
99
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env';
1010
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache';
11-
import { wrapDataFetcherError } from './errors';
11+
import { DataFetcherError, wrapDataFetcherError } from './errors';
1212
import { memoize } from './memoize';
1313
import type { GitBookDataFetcher } from './types';
1414

@@ -95,6 +95,15 @@ export function createDataFetcher(
9595
})
9696
);
9797
},
98+
getRevisionPageMarkdown(params) {
99+
return trace('getRevisionPageMarkdown', () =>
100+
getRevisionPageMarkdown(input, {
101+
spaceId: params.spaceId,
102+
revisionId: params.revisionId,
103+
pageId: params.pageId,
104+
})
105+
);
106+
},
98107
getReusableContent(params) {
99108
return trace('getReusableContent', () =>
100109
getReusableContent(input, {
@@ -324,6 +333,39 @@ const getRevisionFile = memoize(async function getRevisionFile(
324333
});
325334
});
326335

336+
const getRevisionPageMarkdown = memoize(async function getRevisionPageMarkdown(
337+
input: DataFetcherInput,
338+
params: {
339+
spaceId: string;
340+
revisionId: string;
341+
pageId: string;
342+
}
343+
) {
344+
'use cache';
345+
346+
return trace('getRevisionPageMarkdown.uncached', () => {
347+
cacheLife('max');
348+
349+
return wrapDataFetcherError(async () => {
350+
const api = await apiClient(input);
351+
const res = await api.spaces.getPageInRevisionById(
352+
params.spaceId,
353+
params.revisionId,
354+
params.pageId,
355+
{
356+
format: 'markdown',
357+
}
358+
);
359+
360+
if (!('markdown' in res.data)) {
361+
throw new DataFetcherError('Page is not a document', 404);
362+
}
363+
364+
return res.data.markdown;
365+
});
366+
});
367+
});
368+
327369
const getRevisionPageByPath = memoize(async function getRevisionPageByPath(
328370
input: DataFetcherInput,
329371
params: {

packages/gitbook-v2/src/lib/data/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ export interface GitBookDataFetcher {
9797
path: string;
9898
}): Promise<DataFetcherResponse<api.RevisionPageDocument | api.RevisionPageGroup>>;
9999

100+
/**
101+
* Get the markdown content of a page by its path.
102+
*/
103+
getRevisionPageMarkdown(params: {
104+
spaceId: string;
105+
revisionId: string;
106+
pageId: string;
107+
}): Promise<DataFetcherResponse<string>>;
108+
100109
/**
101110
* Get a document by its space ID and document ID.
102111
*/

packages/gitbook-v2/src/middleware.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,13 +389,26 @@ function encodePathInSiteContent(rawPathname: string): {
389389
return { pathname };
390390
}
391391

392+
// If the pathname is a markdown file, we rewrite it to ~gitbook/markdown/:pathname
393+
if (pathname.match(/\.md$/)) {
394+
const pagePathWithoutMD = pathname.slice(0, -3);
395+
return {
396+
pathname: `~gitbook/markdown/${encodeURIComponent(pagePathWithoutMD)}`,
397+
// The markdown content is always static and doesn't depend on the dynamic parameter (customization, theme, etc)
398+
routeType: 'static',
399+
};
400+
}
401+
392402
switch (pathname) {
393403
case '~gitbook/icon':
404+
return { pathname };
394405
case 'llms.txt':
395406
case 'sitemap.xml':
396407
case 'sitemap-pages.xml':
397408
case 'robots.txt':
398-
return { pathname };
409+
// LLMs.txt, sitemap, sitemap-pages and robots.txt are always static
410+
// as they only depend on the site structure / pages.
411+
return { pathname, routeType: 'static' };
399412
case '~gitbook/pdf':
400413
// PDF routes are always dynamic as they depend on the search params.
401414
return { pathname, routeType: 'dynamic' };

packages/gitbook/e2e/internal.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,22 @@ const testCases: TestsCase[] = [
403403
},
404404
],
405405
},
406+
{
407+
name: 'Markdown page',
408+
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
409+
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
410+
tests: [
411+
{
412+
name: 'Text page',
413+
url: 'text-page.md',
414+
screenshot: false,
415+
run: async (_page, response) => {
416+
expect(response?.status()).toBe(200);
417+
expect(response?.headers()['content-type']).toContain('text/markdown');
418+
},
419+
},
420+
],
421+
},
406422
{
407423
name: 'Content tests',
408424
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',

0 commit comments

Comments
 (0)