Skip to content

Commit 9eb829c

Browse files
committed
feat: dynamic markdown routes
1 parent cfea26f commit 9eb829c

File tree

11 files changed

+104
-60
lines changed

11 files changed

+104
-60
lines changed

apps/site/app/[locale]/page.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {
2121
PAGE_VIEWPORT,
2222
DYNAMIC_ROUTES,
23+
DYNAMIC_MARKDOWN_ROUTES,
2324
} from '#site/next.dynamic.constants.mjs';
2425
import { dynamicRouter } from '#site/next.dynamic.mjs';
2526
import { allLocaleCodes, availableLocaleCodes } from '#site/next.locales.mjs';
@@ -97,10 +98,14 @@ const getPage: FC<DynamicParams> = async props => {
9798

9899
const staticGeneratedLayout = DYNAMIC_ROUTES.get(pathname);
99100

100-
// If the current pathname is a statically generated route
101-
// it means it does not have a Markdown file nor exists under the filesystem
102-
// but it is a valid route with an assigned layout that should be rendered
103-
if (staticGeneratedLayout !== undefined) {
101+
// If the current pathname corresponds to a statically generated route but does
102+
// not have a dynamic Markdown file, it means there is no Markdown file on the
103+
// filesystem for it. However, it is still a valid route with an assigned layout
104+
// that should be rendered.
105+
if (
106+
staticGeneratedLayout !== undefined &&
107+
!DYNAMIC_MARKDOWN_ROUTES.has(staticGeneratedLayout)
108+
) {
104109
// Metadata and shared Context to be available through the lifecycle of the page
105110
const sharedContext = { pathname: `/${pathname}` };
106111

apps/site/components/Downloads/DownloadReleasesTable/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const DownloadReleasesTable: FC = () => {
3535
{releaseData.map(release => (
3636
<tr key={release.major}>
3737
<td data-label="Version">
38-
<Link href={`/download/${release.versionWithPrefix}`}>
38+
<Link href={`/download/archive/${release.versionWithPrefix}`}>
3939
v{release.major}
4040
</Link>
4141
</td>

apps/site/components/Downloads/MinorReleasesTable/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ const MinorReleasesTable: FC<MinorReleasesTableProps> = ({ releases }) => {
3838
{releases.map(release => (
3939
<tr key={release.version}>
4040
<td>
41-
<Link kind="neutral" href={`/download/v${release.version}`}>
41+
<Link
42+
kind="neutral"
43+
href={`/download/archive/v${release.version}`}
44+
>
4245
v{release.version}
4346
</Link>
4447
</td>

apps/site/components/withMarkdownContent.tsx

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

apps/site/layouts/DownloadArchive.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
import type { FC } from 'react';
1+
import type { FC, PropsWithChildren } from 'react';
22

33
import WithFooter from '#site/components/withFooter';
4-
import WithMarkdownContent from '#site/components/withMarkdownContent';
54
import WithNavBar from '#site/components/withNavBar';
65

76
import styles from './layouts.module.css';
87

9-
const DownloadArchiveLayout: FC = () => (
8+
const DownloadArchiveLayout: FC<PropsWithChildren> = ({ children }) => (
109
<>
1110
<WithNavBar />
1211

1312
<div className={styles.downloadLayout}>
14-
<main>
15-
<WithMarkdownContent file={['download', 'archive']} />
16-
</main>
13+
<main>{children}</main>
1714
</div>
1815

1916
<WithFooter />
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
import nodevu from '@nodevu/core';
4+
5+
/**
6+
* This method is used to generate all Node.js versions
7+
* for self-consumption during RSC and Static Builds
8+
*
9+
* @returns {Promise<Array<string>>}
10+
*/
11+
const generateAllVersionsData = async () => {
12+
const nodevuOutput = await nodevu({ fetch });
13+
14+
const majors = Object.entries(nodevuOutput).filter(
15+
([version, { support }]) => {
16+
// Filter out those without documented support
17+
// Basically those not in schedule.json
18+
if (!support) {
19+
return false;
20+
}
21+
22+
// nodevu returns duplicated v0.x versions (v0.12, v0.10, ...).
23+
// This behavior seems intentional as the case is hardcoded in nodevu,
24+
// see https://github.com/cutenode/nodevu/blob/0c8538c70195fb7181e0a4d1eeb6a28e8ed95698/core/index.js#L24.
25+
// This line ignores those duplicated versions and takes the latest
26+
// v0.x version (v0.12.18). It is also consistent with the legacy
27+
// nodejs.org implementation.
28+
if (version.startsWith('v0.') && version !== 'v0.12') {
29+
return false;
30+
}
31+
32+
return true;
33+
}
34+
);
35+
36+
const allVersions = [];
37+
38+
majors.forEach(([, major]) => {
39+
Object.entries(major.releases).forEach(([, release]) => {
40+
allVersions.push(`v${release.semver.raw}`);
41+
});
42+
});
43+
44+
return allVersions;
45+
};
46+
47+
export default generateAllVersionsData;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { cache } from 'react';
2+
3+
import generateAllVersionsData from '#site/next-data/generators/releaseVersions.mjs';
4+
5+
const releaseVersions = await generateAllVersionsData();
6+
7+
const provideReleaseVersions = cache(() => releaseVersions);
8+
9+
export default provideReleaseVersions;

apps/site/next.dynamic.constants.mjs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use strict';
22

3-
import provideReleaseData from '#site/next-data/providers/releaseData';
43
import { blogData } from '#site/next.json.mjs';
54

65
import { provideBlogPosts } from './next-data/providers/blogData';
6+
import provideReleaseVersions from './next-data/providers/releaseVersions';
77
import { BASE_PATH, BASE_URL } from './next.constants.mjs';
88
import { siteConfig } from './next.json.mjs';
99
import { defaultLocale } from './next.locales.mjs';
@@ -20,8 +20,6 @@ export const IGNORED_ROUTES = [
2020
locale !== defaultLocale.code && /^blog/.test(pathname),
2121
// This is used to ignore all pathnames that are empty
2222
({ locale, pathname }) => locale.length && !pathname.length,
23-
// This is used to ignore download routes for Node.js versions and downloads archive page
24-
({ pathname }) => /^download\/(v\d+(\.\d+)*|archive)$/.test(pathname),
2523
];
2624

2725
/**
@@ -33,13 +31,11 @@ export const IGNORED_ROUTES = [
3331
*/
3432
export const DYNAMIC_ROUTES = new Map([
3533
// Creates dynamic routes for downloads archive pages for each version
36-
// (e.g., /download/v18.20.8, /download/v20.19.2)
37-
...provideReleaseData()
38-
.flatMap(({ minorVersions, versionWithPrefix }) => [
39-
`download/${versionWithPrefix}`,
40-
...minorVersions.map(minor => `download/${minor.versionWithPrefix}`),
41-
])
42-
.map(version => [version, 'download-archive']),
34+
// (e.g., /download/archive/v18.20.8, /download/archive/v20.19.2)
35+
...provideReleaseVersions().map(version => [
36+
`download/archive/${version}`,
37+
'download-archive',
38+
]),
4339
// Provides Routes for all Blog Categories
4440
...blogData.categories.map(c => [`blog/${c}`, 'blog-category']),
4541
// Provides Routes for all Blog Categories w/ Pagination
@@ -55,6 +51,18 @@ export const DYNAMIC_ROUTES = new Map([
5551
.flat(),
5652
]);
5753

54+
/**
55+
* A Map that stores file paths for Markdown files to be dynamically generated
56+
* in a dynamic route, keyed by their corresponding layout names.
57+
*
58+
* @type {Map<import('./types').Layouts, Array<string>>} A Map of Layout Name and paths
59+
*/
60+
export const DYNAMIC_MARKDOWN_ROUTES = new Map([
61+
// Pages that use the download-archive layout map to the /{locale}/download/archive/index.mdx
62+
// markdown file.
63+
['download-archive', ['download', 'archive']],
64+
]);
65+
5866
/**
5967
* This is the default Next.js Page Metadata for all pages
6068
*

apps/site/next.dynamic.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from './next.constants.mjs';
1717
import {
1818
DYNAMIC_ROUTES,
19+
DYNAMIC_MARKDOWN_ROUTES,
1920
IGNORED_ROUTES,
2021
PAGE_METADATA,
2122
} from './next.dynamic.constants.mjs';
@@ -106,6 +107,15 @@ const getDynamicRouter = async () => {
106107
* @returns {Promise<{ source: string; filename: string }>}
107108
*/
108109
const _getMarkdownFile = async (locale = '', pathname = '') => {
110+
const layout = DYNAMIC_ROUTES.get(pathname);
111+
112+
if (DYNAMIC_MARKDOWN_ROUTES.has(layout)) {
113+
// If the current pathname is a dynamic route that does not have a Markdown
114+
// file we simply return the pathname of the dynamic route so that the page
115+
// can be rendered with the correct layout
116+
pathname = getPathname(DYNAMIC_MARKDOWN_ROUTES.get(layout));
117+
}
118+
109119
const normalizedPathname = normalize(pathname).replace('.', '');
110120

111121
// This verifies if the given pathname actually exists on our Map

apps/site/pages/en/download/archive.mdx renamed to apps/site/pages/en/download/archive/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ layout: download-archive
4040
</ul>
4141

4242
<h3>Other releases</h3>
43-
<WithReleaseSelect placeholder="Select a version" defaultValue={`/download/${version}`} className="w-64"/>
43+
<WithReleaseSelect placeholder="Select a version" defaultValue={`/download/archive/${version}`} className="w-64"/>
4444

4545
<h2>Binary Downloads</h2>
4646
<DownloadsTable source={binaries} />

0 commit comments

Comments
 (0)