-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
feat(mdx): move to next-mdx-remote
#8490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'; |
This file was deleted.
| 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'))!; | ||||||||||||
| file.pathname = `/blog/${cat}`; | ||||||||||||
|
|
||||||||||||
| return renderPage(file); | ||||||||||||
|
Comment on lines
+44
to
+46
|
||||||||||||
| file.pathname = `/blog/${cat}`; | |
| return renderPage(file); | |
| return renderPage({ ...file, pathname: `/blog/${cat}` }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||||||||||||||
|
|
@@ -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(); | ||||||||||||||||||
|
|
@@ -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'))!; | ||||||||||||||||||
|
||||||||||||||||||
| const markdown = (await getMarkdownFile(locale, 'download/archive'))!; | |
| const markdown = await getMarkdownFile(locale, 'download/archive'); | |
| if (!markdown) { | |
| return notFound(); | |
| } |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
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} });
| markdown.pathname = `/download/archive/${version}`; | |
| return renderPage(markdown); | |
| return renderPage({ | |
| ...markdown, | |
| pathname: `/download/archive/${version}`, | |
| }); |
There was a problem hiding this comment.
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
getMarkdownFilereturns 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.