diff --git a/apps/site/app/[locale]/[...path]/page.tsx b/apps/site/app/[locale]/[...path]/page.tsx index 01edf259fe177..68c835fad09b6 100644 --- a/apps/site/app/[locale]/[...path]/page.tsx +++ b/apps/site/app/[locale]/[...path]/page.tsx @@ -7,14 +7,18 @@ * dynamic params, which will lead on static export errors and other sort of issues. */ -import * as basePage from '#site/app/[locale]/page'; -import { - ENABLE_STATIC_EXPORT_LOCALE, - ENABLE_STATIC_EXPORT, -} from '#site/next.constants.mjs'; +import { notFound } from 'next/navigation'; +import type { FC } from 'react'; + +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 { availableLocaleCodes, defaultLocale } from '#site/next.locales.mjs'; +type DynamicStaticPaths = { path: Array; locale: string }; +type DynamicParams = { params: Promise }; + // This is the default Viewport Metadata // @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function export const generateViewport = basePage.generateViewport; @@ -34,14 +38,11 @@ export const generateStaticParams = async () => { return []; } - // Helper function to fetch and map routes for a specific locale - const getRoutesForLocale = async (locale: string) => { - const routes = await dynamicRouter.getRoutesByLanguage(locale); + const routes = await dynamicRouter.getAllRoutes(); - return routes.map(pathname => - dynamicRouter.mapPathToRoute(locale, pathname) - ); - }; + // 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 @@ -49,9 +50,35 @@ export const generateStaticParams = async () => { : [defaultLocale.code]; // Generates all possible routes for all available locales - const routes = await Promise.all(locales.map(getRoutesForLocale)); + 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 = async props => { + // Gets the current full pathname for a given path + const [locale, pathname] = await basePage.getLocaleAndPath(props); + + // 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: content, + layout: context.frontmatter.layout, + context: context, + }); + } - return routes.flat().sort(); + return notFound(); }; // Enforces that this route is used as static rendering @@ -64,4 +91,4 @@ export const dynamic = 'force-static'; // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate export const revalidate = 300; -export default basePage.default; +export default getPage; diff --git a/apps/site/app/[locale]/blog/[...path]/page.tsx b/apps/site/app/[locale]/blog/[...path]/page.tsx new file mode 100644 index 0000000000000..f8fd5ea520fb5 --- /dev/null +++ b/apps/site/app/[locale]/blog/[...path]/page.tsx @@ -0,0 +1,78 @@ +import { notFound } from 'next/navigation'; +import type { FC } from 'react'; + +import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs'; +import { BLOG_DYNAMIC_ROUTES } from '#site/next.dynamic.constants.mjs'; +import * as basePage from '#site/next.dynamic.page.mjs'; +import { defaultLocale } from '#site/next.locales.mjs'; + +type DynamicStaticPaths = { path: Array; locale: string }; +type DynamicParams = { params: Promise }; + +// 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 []; + } + + return BLOG_DYNAMIC_ROUTES.map(pathname => ({ + locale: defaultLocale.code, + path: pathname.split('/'), + })); +}; + +// 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 = async props => { + // Gets the current full pathname for a given path + const [locale, pathname] = await basePage.getLocaleAndPath(props); + + // Verifies if the current route is a dynamic route + const isDynamicRoute = BLOG_DYNAMIC_ROUTES.some(r => r.includes(pathname)); + + // Gets the Markdown content and context for Blog pages + // otherwise this is likely a blog-category or a blog post + const [content, context] = await basePage.getMarkdownContext({ + locale: locale, + pathname: `blog/${pathname}`, + }); + + // If this isn't a valid dynamic route for blog post or there's no mardown file + // for this, then we fail as not found as there's nothing we can do. + if (isDynamicRoute || context.filename) { + return basePage.renderPage({ + content: content, + layout: context.frontmatter.layout ?? 'blog-category', + context: { ...context, pathname: `/blog/${pathname}` }, + }); + } + + 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; diff --git a/apps/site/app/[locale]/next-data/page-data/route.ts b/apps/site/app/[locale]/next-data/page-data/route.ts index a9ad5a7e4416c..58d82a27e5993 100644 --- a/apps/site/app/[locale]/next-data/page-data/route.ts +++ b/apps/site/app/[locale]/next-data/page-data/route.ts @@ -11,9 +11,7 @@ import { parseRichTextIntoPlainText } from '#site/util/string'; // @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers export const GET = async () => { // Retrieves all available routes for the default locale - const allAvailbleRoutes = await dynamicRouter.getRoutesByLanguage( - defaultLocale.code - ); + const allAvailbleRoutes = await dynamicRouter.getAllRoutes(); // We exclude the blog routes from the available pages metadata // as they are generated separately and are not part of the static pages diff --git a/apps/site/app/[locale]/page.tsx b/apps/site/app/[locale]/page.tsx index cf9d20c1de3b3..482d917122c3e 100644 --- a/apps/site/app/[locale]/page.tsx +++ b/apps/site/app/[locale]/page.tsx @@ -1,53 +1,31 @@ -/** - * This file contains the logic for rendering our dynamic and static routes within the Node.js Website - * this page route template is used to render the entry points for each locale and then also reused within - * the [...path] route to render the individual pages under each locale of the 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 { notFound, redirect } from 'next/navigation'; -import { setRequestLocale } from 'next-intl/server'; +import { notFound } from 'next/navigation'; import type { FC } from 'react'; -import { setClientContext } from '#site/client-context'; -import WithLayout from '#site/components/withLayout'; -import { - ENABLE_STATIC_EXPORT_LOCALE, - ENABLE_STATIC_EXPORT, -} from '#site/next.constants.mjs'; -import { - PAGE_VIEWPORT, - DYNAMIC_ROUTES, -} from '#site/next.dynamic.constants.mjs'; -import { dynamicRouter } from '#site/next.dynamic.mjs'; -import { allLocaleCodes, availableLocaleCodes } from '#site/next.locales.mjs'; +import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs'; +import { ENABLE_STATIC_EXPORT_LOCALE } from '#site/next.constants.mjs'; +import * as basePage from '#site/next.dynamic.page.mjs'; +import { availableLocaleCodes } from '#site/next.locales.mjs'; import { defaultLocale } from '#site/next.locales.mjs'; -import { MatterProvider } from '#site/providers/matterProvider'; type DynamicStaticPaths = { path: Array; locale: string }; type DynamicParams = { params: Promise }; // This is the default Viewport Metadata // @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function -export const generateViewport = () => ({ ...PAGE_VIEWPORT }); +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 = async (props: DynamicParams) => { - const { path = [], locale = defaultLocale.code } = await props.params; - - const pathname = dynamicRouter.getPathname(path); - - return dynamicRouter.getPageMetadata(locale, pathname); -}; +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 +/** + * 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) { @@ -61,7 +39,7 @@ export const generateStaticParams = async () => { const routes = await Promise.all( // Gets all mapped routes to the Next.js Routing Engine by Locale - locales.map((locale: string) => ({ locale })) + locales.map(locale => ({ locale })) ); return routes.flat().sort(); @@ -72,87 +50,22 @@ 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 = async props => { - const { path = [], locale = defaultLocale.code } = await props.params; - - if (!availableLocaleCodes.includes(locale)) { - // Forces the current locale to be the Default Locale - setRequestLocale(defaultLocale.code); - - if (!allLocaleCodes.includes(locale)) { - // when the locale is not listed in the locales, return NotFound - return notFound(); - } - - // Redirect to the default locale path - const pathname = dynamicRouter.getPathname(path); - - return redirect(`/${defaultLocale.code}/${pathname}`); - } - - // Configures the current Locale to be the given Locale of the Request - setRequestLocale(locale); - // Gets the current full pathname for a given path - const pathname = dynamicRouter.getPathname(path); - - const staticGeneratedLayout = DYNAMIC_ROUTES.get(pathname); + const [locale, pathname] = await basePage.getLocaleAndPath(props); - // If the current pathname is a statically generated route - // it means it does not have a Markdown file nor exists under the filesystem - // but it is a valid route with an assigned layout that should be rendered - if (staticGeneratedLayout !== undefined) { - // Metadata and shared Context to be available through the lifecycle of the page - const sharedContext = { pathname: `/${pathname}` }; - - // Defines a shared Server Context for the Client-Side - // That is shared for all pages under the dynamic router - setClientContext(sharedContext); - - // The Matter Provider allows Client-Side injection of the data - // to a shared React Client Provider even though the page is rendered - // within a server-side context - return ( - - - - ); - } - - // We retrieve the source of the Markdown file by doing an educated guess - // of what possible files could be the source of the page, since the extension - // context is lost from `getStaticProps` as a limitation of Next.js itself - const { source, filename } = await dynamicRouter.getMarkdownFile( + // Gets the Markdown content and context + const [content, context] = await basePage.getMarkdownContext({ locale, - pathname - ); - - if (source.length && filename.length) { - // This parses the source Markdown content and returns a React Component and - // relevant context from the Markdown File - const { content, frontmatter, headings, readingTime } = - await dynamicRouter.getMDXContent(source, filename); - - // Metadata and shared Context to be available through the lifecycle of the page - const sharedContext = { - frontmatter: frontmatter, - headings: headings, - pathname: `/${pathname}`, - readingTime: readingTime, - filename: filename, - }; - - // Defines a shared Server Context for the Client-Side - // That is shared for all pages under the dynamic router - setClientContext(sharedContext); - - // The Matter Provider allows Client-Side injection of the data - // to a shared React Client Provider even though the page is rendered - // within a server-side context - return ( - - {content} - - ); + pathname, + }); + + // If we have a filename and layout then we have a page + if (context.filename && context.frontmatter.layout) { + return basePage.renderPage({ + content: content, + layout: context.frontmatter.layout, + context: context, + }); } return notFound(); diff --git a/apps/site/app/sitemap.ts b/apps/site/app/sitemap.ts index 86b8c337a7290..f53d2c65d9569 100644 --- a/apps/site/app/sitemap.ts +++ b/apps/site/app/sitemap.ts @@ -1,64 +1,42 @@ import type { MetadataRoute } from 'next'; -import { - BASE_PATH, - BASE_URL, - EXTERNAL_LINKS_SITEMAP, -} from '#site/next.constants.mjs'; +import { BASE_PATH } from '#site/next.constants.mjs'; +import { BASE_URL } from '#site/next.constants.mjs'; +import { EXTERNAL_LINKS_SITEMAP } from '#site/next.constants.mjs'; +import { BLOG_DYNAMIC_ROUTES } from '#site/next.dynamic.constants.mjs'; import { dynamicRouter } from '#site/next.dynamic.mjs'; import { availableLocaleCodes, defaultLocale } from '#site/next.locales.mjs'; // This is the combination of the Application Base URL and Base PATH const baseUrlAndPath = `${BASE_URL}${BASE_PATH}`; +// All available alternate locales +const nonDefaultLocales = availableLocaleCodes.filter( + l => l !== defaultLocale.code +); + +const getAlternatePath = (r: string, locales: Array) => + Object.fromEntries(locales.map(l => [l, `${baseUrlAndPath}/${l}/${r}`])); + // This allows us to generate a `sitemap.xml` file dynamically based on the needs of the Node.js Website const sitemap = async (): Promise => { - const routes = await dynamicRouter.getRoutesByLanguage(defaultLocale.code); - const paths = []; + // Gets a list of all statically available routes + const routes = await dynamicRouter.getAllRoutes(); const currentDate = new Date().toISOString(); - for (const route of routes) { - const availableLocalesForRoute = []; - - for (const locale of availableLocaleCodes.filter( - locale => locale !== defaultLocale.code - )) { - const markdownFile = await dynamicRouter.getMarkdownFile(locale, route); - const isAvailable = markdownFile.filename !== ''; - if (isAvailable) { - availableLocalesForRoute.push(locale); - } - } - - const alternatesPaths = availableLocalesForRoute.reduce( - (acc, locale) => ({ - ...acc, - [locale]: `${baseUrlAndPath}/${locale}/${route}`, - }), - {} - ); + const getSitemapEntry = (r: string, locales: Array = []) => ({ + url: `${baseUrlAndPath}/${defaultLocale.code}/${r}`, + lastModified: currentDate, + changeFrequency: 'always' as const, + alternates: { languages: getAlternatePath(r, locales) }, + }); - paths.push({ - url: `${baseUrlAndPath}/${defaultLocale.code}/${route}`, - lastModified: currentDate, - changeFrequency: 'always' as const, - alternates: { - languages: { - ...alternatesPaths, - }, - }, - }); - } + const staticPaths = routes.map(r => getSitemapEntry(r, nonDefaultLocales)); + const blogPaths = BLOG_DYNAMIC_ROUTES.map(r => getSitemapEntry(`blog/${r}`)); + const externalPaths = EXTERNAL_LINKS_SITEMAP.map(r => getSitemapEntry(r)); - return [ - ...paths, - ...EXTERNAL_LINKS_SITEMAP.map(route => ({ - url: route, - lastModified: currentDate, - changeFrequency: 'always' as const, - })), - ]; + return [...staticPaths, ...blogPaths, ...externalPaths]; }; export default sitemap; diff --git a/apps/site/next.dynamic.constants.mjs b/apps/site/next.dynamic.constants.mjs index 294344b09ea15..ce9f3128e0077 100644 --- a/apps/site/next.dynamic.constants.mjs +++ b/apps/site/next.dynamic.constants.mjs @@ -1,48 +1,31 @@ 'use strict'; +import { provideBlogPosts } from '#site/next-data/providers/blogData'; import { blogData } from '#site/next.json.mjs'; -import { provideBlogPosts } from './next-data/providers/blogData'; import { BASE_PATH, BASE_URL } from './next.constants.mjs'; import { siteConfig } from './next.json.mjs'; -import { defaultLocale } from './next.locales.mjs'; - -/** - * This is a list of all static routes or pages from the Website that we do not - * want to allow to be statically built on our Static Export Build. - * - * @type {Array<((route: import('./types').RouteSegment) => boolean)>} A list of Ignored Routes by Regular Expressions - */ -export const IGNORED_ROUTES = [ - // This is used to ignore all blog routes except for the English language - ({ locale, pathname }) => - locale !== defaultLocale.code && /^blog/.test(pathname), - // This is used to ignore all pathnames that are empty - ({ locale, pathname }) => locale.length && !pathname.length, -]; /** * This constant is used to create static routes on-the-fly that do not have a file-system * counterpart route. This is useful for providing routes with matching Layout Names * but that do not have Markdown content and a matching file for the route * - * @type {Map} A Map of pathname and Layout Name + * @type {Array} A Map of pathname and Layout Name */ -export const DYNAMIC_ROUTES = new Map([ +export const BLOG_DYNAMIC_ROUTES = [ // Provides Routes for all Blog Categories - ...blogData.categories.map(c => [`blog/${c}`, 'blog-category']), + ...blogData.categories, // Provides Routes for all Blog Categories w/ Pagination ...blogData.categories // retrieves the amount of pages for each blog category .map(c => [c, provideBlogPosts(c).pagination.pages]) // creates a numeric array for each page and define a pathname for // each page for a category (i.e. blog/all/page/1) - .map(([c, t]) => [...Array(t).keys()].map(p => `blog/${c}/page/${p + 1}`)) - // creates a tuple of each pathname and layout for the route - .map(paths => paths.map(path => [path, 'blog-category'])) + .map(([c, t]) => [...Array(t).keys()].map(p => `${c}/page/${p + 1}`)) // flattens the array since we have a .map inside another .map .flat(), -]); +]; /** * This is the default Next.js Page Metadata for all pages diff --git a/apps/site/next.dynamic.mjs b/apps/site/next.dynamic.mjs index eddacdaaeaebc..4b80852e12d9b 100644 --- a/apps/site/next.dynamic.mjs +++ b/apps/site/next.dynamic.mjs @@ -7,18 +7,12 @@ import matter from 'gray-matter'; import { cache } from 'react'; import { VFile } from 'vfile'; -import { - BASE_PATH, - BASE_URL, - DEFAULT_CATEGORY_OG_TYPE, - ENABLE_STATIC_EXPORT, - IS_DEV_ENV, -} from './next.constants.mjs'; -import { - DYNAMIC_ROUTES, - IGNORED_ROUTES, - PAGE_METADATA, -} from './next.dynamic.constants.mjs'; +import { BASE_PATH } from './next.constants.mjs'; +import { BASE_URL } from './next.constants.mjs'; +import { DEFAULT_CATEGORY_OG_TYPE } from './next.constants.mjs'; +import { ENABLE_STATIC_EXPORT } from './next.constants.mjs'; +import { IS_DEV_ENV } from './next.constants.mjs'; +import { PAGE_METADATA } from './next.dynamic.constants.mjs'; import { getMarkdownFiles } from './next.helpers.mjs'; import { siteConfig } from './next.json.mjs'; import { availableLocaleCodes, defaultLocale } from './next.locales.mjs'; @@ -82,19 +76,13 @@ const getDynamicRouter = async () => { }); /** - * This method returns a list of all routes that exist for a given locale - * - * @param {string} locale + * This method returns a list of all routes that exist + * Note: It will only match routes that have at least one pathname. + * @returns {Promise>} */ - const getRoutesByLanguage = async (locale = defaultLocale.code) => { - const shouldIgnoreStaticRoute = pathname => - IGNORED_ROUTES.every(e => !e({ pathname, locale })); - - return [...pathnameToFilename.keys()] - .filter(shouldIgnoreStaticRoute) - .concat([...DYNAMIC_ROUTES.keys()]); - }; + const getAllRoutes = async () => + [...pathnameToFilename.keys()].filter(pathname => pathname.length); /** * This method attempts to retrieve either a localized Markdown file @@ -271,7 +259,7 @@ const getDynamicRouter = async () => { return { mapPathToRoute, getPathname, - getRoutesByLanguage, + getAllRoutes, getMDXContent, getMarkdownFile, getPageMetadata, diff --git a/apps/site/next.dynamic.page.mjs b/apps/site/next.dynamic.page.mjs new file mode 100644 index 0000000000000..53c583ea29030 --- /dev/null +++ b/apps/site/next.dynamic.page.mjs @@ -0,0 +1,119 @@ +import { notFound, redirect } from 'next/navigation'; +import { setRequestLocale } from 'next-intl/server'; + +import { setClientContext } from '#site/client-context'; +import WithLayout from '#site/components/withLayout'; +import { PAGE_VIEWPORT } from '#site/next.dynamic.constants.mjs'; +import { dynamicRouter } from '#site/next.dynamic.mjs'; +import { allLocaleCodes, availableLocaleCodes } from '#site/next.locales.mjs'; +import { defaultLocale } from '#site/next.locales.mjs'; +import { MatterProvider } from '#site/providers/matterProvider'; + +/** + * This is the default Viewport Metadata + * + * @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function + * + * @returns {import('next').Viewport} the default viewport metadata + */ +export const generateViewport = () => ({ ...PAGE_VIEWPORT }); + +/** + * This generates each page's HTML Metadata + * + * @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata + * + * @param {{ params: Promise<{ path: Array; locale: string }> }} props + * @returns {Promise} the metadata for the page + */ +export const generateMetadata = async props => { + const { path = [], locale = defaultLocale.code } = await props.params; + + const pathname = dynamicRouter.getPathname(path); + + return dynamicRouter.getPageMetadata(locale, pathname); +}; + +/** + * This method is used for retrieving the current locale and pathname from the request + * + * @param {{ params: Promise<{ path: Array; locale: string }> }} props + * @returns {Promise<[string, string]>} the locale and pathname for the request + */ +export const getLocaleAndPath = async props => { + const { path = [], locale = defaultLocale.code } = await props.params; + + if (!availableLocaleCodes.includes(locale)) { + // Forces the current locale to be the Default Locale + setRequestLocale(defaultLocale.code); + + if (!allLocaleCodes.includes(locale)) { + // when the locale is not listed in the locales, return NotFound + return notFound(); + } + + // Redirect to the default locale path + const pathname = dynamicRouter.getPathname(path); + + return redirect(`/${defaultLocale.code}/${pathname}`); + } + + // Configures the current Locale to be the given Locale of the Request + setRequestLocale(locale); + + // Gets the current full pathname for a given path + return [locale, dynamicRouter.getPathname(path)]; +}; + +/** + * This method is used for retrieving the Markdown content and context + * + * @param {{ locale: string; pathname: string }} props + * @returns {Promise<[import('react').ReactNode, import('#site/types/server').ClientSharedServerContext]>} + */ +export const getMarkdownContext = async props => { + // We retrieve the source of the Markdown file by doing an educated guess + // of what possible files could be the source of the page, since the extension + // context is lost from `getStaticProps` as a limitation of Next.js itself + const { source, filename } = await dynamicRouter.getMarkdownFile( + props.locale, + props.pathname + ); + + // This parses the source Markdown content and returns a React Component and + // relevant context from the Markdown File + const { content, frontmatter, headings, readingTime } = + await dynamicRouter.getMDXContent(source, filename); + + // Metadata and shared Context to be available through the lifecycle of the page + const context = { + frontmatter: frontmatter, + headings: headings, + pathname: `/${props.pathname}`, + readingTime: readingTime, + filename: filename, + }; + + return [content, context]; +}; + +/** + * This method is used for rendering the actual page + * + * @param {{ content: import('react').ReactNode; layout: import('#site/types/layouts').Layouts; context: Partial; }} props + * @returns {import('react').ReactElement} + */ +export const renderPage = props => { + // Defines a shared Server Context for the Client-Side + // That is shared for all pages under the dynamic router + setClientContext(props.context); + + // The Matter Provider allows Client-Side injection of the data + // to a shared React Client Provider even though the page is rendered + // within a server-side context + return ( + + {props.content} + + ); +};