Skip to content
Merged
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
57 changes: 42 additions & 15 deletions apps/site/app/[locale]/[...path]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>; locale: string };
type DynamicParams = { params: Promise<DynamicStaticPaths> };

// This is the default Viewport Metadata
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function
export const generateViewport = basePage.generateViewport;
Expand All @@ -34,24 +38,47 @@ 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
? availableLocaleCodes
: [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<DynamicParams> = 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
Expand All @@ -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;
78 changes: 78 additions & 0 deletions apps/site/app/[locale]/blog/[...path]/page.tsx
Original file line number Diff line number Diff line change
@@ -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<string>; locale: string };
type DynamicParams = { params: Promise<DynamicStaticPaths> };

// 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<DynamicParams> = 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;
4 changes: 1 addition & 3 deletions apps/site/app/[locale]/next-data/page-data/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
145 changes: 29 additions & 116 deletions apps/site/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -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<string>; locale: string };
type DynamicParams = { params: Promise<DynamicStaticPaths> };

// 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) {
Expand All @@ -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();
Expand All @@ -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<DynamicParams> = 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 (
<MatterProvider {...sharedContext}>
<WithLayout layout={staticGeneratedLayout} />
</MatterProvider>
);
}

// 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 (
<MatterProvider {...sharedContext}>
<WithLayout layout={frontmatter.layout}>{content}</WithLayout>
</MatterProvider>
);
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();
Expand Down
Loading
Loading