Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 2 additions & 97 deletions apps/site/app/[locale]/[...path]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,2 @@
/**
* This file extends on the `page.tsx` file, which is the default file that is used to render
* the entry points for each locale and then also reused within the [...path] route to render the
* and contains all logic for rendering our dynamic and static routes within the Node.js Website.
*
* Note: that each `page.tsx` should have its own `generateStaticParams` to prevent clash of
* dynamic params, which will lead on static export errors and other sort of issues.
*/

import { availableLocaleCodes, defaultLocale } from '@node-core/website-i18n';
import { notFound } from 'next/navigation';

import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs';
import { ENABLE_STATIC_EXPORT_LOCALE } from '#site/next.constants.mjs';
import { dynamicRouter } from '#site/next.dynamic.mjs';
import * as basePage from '#site/next.dynamic.page.mjs';

import type { DynamicParams } from '#site/types';
import type { FC } from 'react';

type PageParams = DynamicParams<{ path: Array<string> }>;

// This is the default Viewport Metadata
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function
export const generateViewport = basePage.generateViewport;

// This generates each page's HTML Metadata
// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata
export const generateMetadata = basePage.generateMetadata;

// Generates all possible static paths based on the locales and environment configuration
// - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false)
// - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales
// - Otherwise, generates paths only for the default locale
// @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params
export const generateStaticParams = async () => {
// Return an empty array if static export is disabled
if (!ENABLE_STATIC_EXPORT) {
return [];
}

const routes = await dynamicRouter.getAllRoutes();

// Helper function to fetch and map routes for a specific locale
const getRoutesForLocale = async (l: string) =>
routes.map(pathname => dynamicRouter.mapPathToRoute(l, pathname));

// Determine which locales to include in the static export
const locales = ENABLE_STATIC_EXPORT_LOCALE
? availableLocaleCodes
: [defaultLocale.code];

// Generates all possible routes for all available locales
const routesWithLocales = await Promise.all(locales.map(getRoutesForLocale));

return routesWithLocales.flat().sort();
};

// This method parses the current pathname and does any sort of modifications needed on the route
// then it proceeds to retrieve the Markdown file and parse the MDX Content into a React Component
// finally it returns (if the locale and route are valid) the React Component with the relevant context
// and attached context providers for rendering the current page
const getPage: FC<PageParams> = async props => {
const { path, locale: routeLocale } = await props.params;

// Gets the current full pathname for a given path
const [locale, pathname] = basePage.getLocaleAndPath(path, routeLocale);

// Gets the Markdown content and context
const [content, context] = await basePage.getMarkdownContext({
locale,
pathname,
});

// If we have a filename and layout then we have a page
if (context.filename && context.frontmatter.layout) {
return basePage.renderPage({
content,
layout: context.frontmatter.layout,
context,
});
}

return notFound();
};

// Enforces that this route is used as static rendering
// Except whenever on the Development mode as we want instant-refresh when making changes
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'force-static';

// Ensures that this endpoint is invalidated and re-executed every X minutes
// so that when new deployments happen, the data is refreshed
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
export const revalidate = 300;

export default getPage;
export * from '../page';
export { default } from '../page';
82 changes: 0 additions & 82 deletions apps/site/app/[locale]/blog/[...path]/page.tsx

This file was deleted.

53 changes: 53 additions & 0 deletions apps/site/app/[locale]/blog/[cat]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { defaultLocale } from '@node-core/website-i18n';
import { notFound } from 'next/navigation';

import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs';
import { blogData } from '#site/next.json.mjs';
import { getMarkdownFile } from '#site/router';
import { BLOG_DYNAMIC_ROUTES } from '#site/router/constants';
import { renderPage } from '#site/router/render';

import type { DynamicParams } from '#site/types';
import type { FC } from 'react';

type PageParams = DynamicParams<{ cat: string }>;

// Generates all possible static paths based on the locales and environment configuration
// - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false)
// - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales
// - Otherwise, generates paths only for the default locale
// @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params
export const generateStaticParams = async () => {
// Return an empty array if static export is disabled
if (!ENABLE_STATIC_EXPORT) {
return [];
}

return blogData.categories.map(cat => ({
locale: defaultLocale.code,
cat,
}));
};

// This method parses the current pathname and does any sort of modifications needed on the route
// then it proceeds to retrieve the Markdown file and parse the MDX Content into a React Component
// finally it returns (if the locale and route are valid) the React Component with the relevant context
// and attached context providers for rendering the current page
const getPage: FC<PageParams> = async props => {
const { cat, locale } = await props.params;

// Verifies if the current route is a dynamic route
const isDynamicRoute = BLOG_DYNAMIC_ROUTES.some(r => r.includes(cat));

if (isDynamicRoute) {
const file = (await getMarkdownFile(locale, 'blog'))!;
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-null assertion operator (!) is used here without proper null checking. If getMarkdownFile returns null (e.g., the file doesn't exist), this will throw a runtime error. The code should check if the file exists before using it.

Suggested change
const file = (await getMarkdownFile(locale, 'blog'))!;
const file = await getMarkdownFile(locale, 'blog');
if (!file) {
return notFound();
}

Copilot uses AI. Check for mistakes.
file.pathname = `/blog/${cat}`;

return renderPage(file);
Comment on lines +44 to +46
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown object returned from getMarkdownFile is being mutated directly. If the markdown file is cached (which it is via the cache wrapper in router/index.ts), this mutation could affect all consumers of the cached value.

Consider creating a new object with the updated pathname instead of mutating the original: return renderPage({ ...markdown, pathname: '/blog/${cat}' });

Suggested change
file.pathname = `/blog/${cat}`;
return renderPage(file);
return renderPage({ ...file, pathname: `/blog/${cat}` });

Copilot uses AI. Check for mistakes.
}

return notFound();
};

export * from '../../page';
export default getPage;
46 changes: 10 additions & 36 deletions apps/site/app/[locale]/download/archive/[version]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@ import { notFound, redirect } from 'next/navigation';
import provideReleaseData from '#site/next-data/providers/releaseData';
import provideReleaseVersions from '#site/next-data/providers/releaseVersions';
import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs';
import * as basePage from '#site/next.dynamic.page.mjs';
import { getMarkdownFile } from '#site/router';
import { renderPage } from '#site/router/render';

import type { DynamicParams } from '#site/types';
import type { FC } from 'react';

type PageParams = DynamicParams<{ version: string }>;

// This is the default Viewport Metadata
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function
export const generateViewport = basePage.generateViewport;

// This generates each page's HTML Metadata
// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata
export const generateMetadata = basePage.generateMetadata;

// Generates all possible static paths based on the locales and environment configuration
// - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false)
// - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales
Expand All @@ -43,10 +36,7 @@ export const generateStaticParams = async () => {
// finally it returns (if the locale and route are valid) the React Component with the relevant context
// and attached context providers for rendering the current page
const getPage: FC<PageParams> = async props => {
const { version, locale: routeLocale } = await props.params;

// Gets the current full pathname for a given path
const [locale, pathname] = basePage.getLocaleAndPath(version, routeLocale);
const { version, locale } = await props.params;

if (version === 'current') {
const releaseData = await provideReleaseData();
Expand All @@ -59,35 +49,19 @@ const getPage: FC<PageParams> = async props => {
const versions = await provideReleaseVersions();

// Verifies if the current route is a dynamic route
const isDynamicRoute = versions.some(r => r.includes(pathname));

// Gets the Markdown content and context for Download Archive pages
const [content, context] = await basePage.getMarkdownContext({
locale,
pathname: 'download/archive',
});
const isDynamicRoute = versions.some(r => r.includes(version));

// If this isn't a valid dynamic route for archive version or there's no markdown
// file for this, then we fail as not found as there's nothing we can do.
if (isDynamicRoute && context.filename) {
return basePage.renderPage({
content,
layout: context.frontmatter.layout!,
context: { ...context, pathname: `/download/archive/${pathname}` },
});
if (isDynamicRoute) {
const markdown = (await getMarkdownFile(locale, 'download/archive'))!;
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-null assertion operator (!) is used here without proper null checking. If getMarkdownFile returns null (e.g., the file doesn't exist), this will throw a runtime error. The assertion should be replaced with proper error handling or null checking before the mutation occurs.

Suggested change
const markdown = (await getMarkdownFile(locale, 'download/archive'))!;
const markdown = await getMarkdownFile(locale, 'download/archive');
if (!markdown) {
return notFound();
}

Copilot uses AI. Check for mistakes.
markdown.pathname = `/download/archive/${version}`;

return renderPage(markdown);
Comment on lines +58 to +60
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown object returned from getMarkdownFile is being mutated directly. If the markdown file is cached (which it is via the cache wrapper in router/index.ts), this mutation could affect all consumers of the cached value.

Consider creating a new object with the updated pathname instead of mutating the original: return renderPage({ ...markdown, pathname: /download/archive/${version} });

Suggested change
markdown.pathname = `/download/archive/${version}`;
return renderPage(markdown);
return renderPage({
...markdown,
pathname: `/download/archive/${version}`,
});

Copilot uses AI. Check for mistakes.
}

return notFound();
};

// Enforces that this route is used as static rendering
// Except whenever on the Development mode as we want instant-refresh when making changes
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'force-static';

// Ensures that this endpoint is invalidated and re-executed every X minutes
// so that when new deployments happen, the data is refreshed
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
export const revalidate = 300;

export default getPage;
export * from '#site/router/page';
Loading
Loading