Skip to content

Commit 1335536

Browse files
authored
chore: get rid of hacky dynamic pages, use proper app router (#8086)
* chore: get rid of hacky dynamic pages, use proper app router * fix: fixed route generation * chore: self review changes * fix: fixed static deployment * chore: more cleanup and improvements
1 parent f7e9126 commit 1335536

8 files changed

Lines changed: 311 additions & 227 deletions

File tree

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

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@
77
* dynamic params, which will lead on static export errors and other sort of issues.
88
*/
99

10-
import * as basePage from '#site/app/[locale]/page';
11-
import {
12-
ENABLE_STATIC_EXPORT_LOCALE,
13-
ENABLE_STATIC_EXPORT,
14-
} from '#site/next.constants.mjs';
10+
import { notFound } from 'next/navigation';
11+
import type { FC } from 'react';
12+
13+
import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs';
14+
import { ENABLE_STATIC_EXPORT_LOCALE } from '#site/next.constants.mjs';
1515
import { dynamicRouter } from '#site/next.dynamic.mjs';
16+
import * as basePage from '#site/next.dynamic.page.mjs';
1617
import { availableLocaleCodes, defaultLocale } from '#site/next.locales.mjs';
1718

19+
type DynamicStaticPaths = { path: Array<string>; locale: string };
20+
type DynamicParams = { params: Promise<DynamicStaticPaths> };
21+
1822
// This is the default Viewport Metadata
1923
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function
2024
export const generateViewport = basePage.generateViewport;
@@ -34,24 +38,47 @@ export const generateStaticParams = async () => {
3438
return [];
3539
}
3640

37-
// Helper function to fetch and map routes for a specific locale
38-
const getRoutesForLocale = async (locale: string) => {
39-
const routes = await dynamicRouter.getRoutesByLanguage(locale);
41+
const routes = await dynamicRouter.getAllRoutes();
4042

41-
return routes.map(pathname =>
42-
dynamicRouter.mapPathToRoute(locale, pathname)
43-
);
44-
};
43+
// Helper function to fetch and map routes for a specific locale
44+
const getRoutesForLocale = async (l: string) =>
45+
routes.map(pathname => dynamicRouter.mapPathToRoute(l, pathname));
4546

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

5152
// Generates all possible routes for all available locales
52-
const routes = await Promise.all(locales.map(getRoutesForLocale));
53+
const routesWithLocales = await Promise.all(locales.map(getRoutesForLocale));
54+
55+
return routesWithLocales.flat().sort();
56+
};
57+
58+
// This method parses the current pathname and does any sort of modifications needed on the route
59+
// then it proceeds to retrieve the Markdown file and parse the MDX Content into a React Component
60+
// finally it returns (if the locale and route are valid) the React Component with the relevant context
61+
// and attached context providers for rendering the current page
62+
const getPage: FC<DynamicParams> = async props => {
63+
// Gets the current full pathname for a given path
64+
const [locale, pathname] = await basePage.getLocaleAndPath(props);
65+
66+
// Gets the Markdown content and context
67+
const [content, context] = await basePage.getMarkdownContext({
68+
locale,
69+
pathname,
70+
});
71+
72+
// If we have a filename and layout then we have a page
73+
if (context.filename && context.frontmatter.layout) {
74+
return basePage.renderPage({
75+
content: content,
76+
layout: context.frontmatter.layout,
77+
context: context,
78+
});
79+
}
5380

54-
return routes.flat().sort();
81+
return notFound();
5582
};
5683

5784
// Enforces that this route is used as static rendering
@@ -64,4 +91,4 @@ export const dynamic = 'force-static';
6491
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
6592
export const revalidate = 300;
6693

67-
export default basePage.default;
94+
export default getPage;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { notFound } from 'next/navigation';
2+
import type { FC } from 'react';
3+
4+
import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs';
5+
import { BLOG_DYNAMIC_ROUTES } from '#site/next.dynamic.constants.mjs';
6+
import * as basePage from '#site/next.dynamic.page.mjs';
7+
import { defaultLocale } from '#site/next.locales.mjs';
8+
9+
type DynamicStaticPaths = { path: Array<string>; locale: string };
10+
type DynamicParams = { params: Promise<DynamicStaticPaths> };
11+
12+
// This is the default Viewport Metadata
13+
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function
14+
export const generateViewport = basePage.generateViewport;
15+
16+
// This generates each page's HTML Metadata
17+
// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata
18+
export const generateMetadata = basePage.generateMetadata;
19+
20+
// Generates all possible static paths based on the locales and environment configuration
21+
// - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false)
22+
// - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales
23+
// - Otherwise, generates paths only for the default locale
24+
// @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params
25+
export const generateStaticParams = async () => {
26+
// Return an empty array if static export is disabled
27+
if (!ENABLE_STATIC_EXPORT) {
28+
return [];
29+
}
30+
31+
return BLOG_DYNAMIC_ROUTES.map(pathname => ({
32+
locale: defaultLocale.code,
33+
path: pathname.split('/'),
34+
}));
35+
};
36+
37+
// This method parses the current pathname and does any sort of modifications needed on the route
38+
// then it proceeds to retrieve the Markdown file and parse the MDX Content into a React Component
39+
// finally it returns (if the locale and route are valid) the React Component with the relevant context
40+
// and attached context providers for rendering the current page
41+
const getPage: FC<DynamicParams> = async props => {
42+
// Gets the current full pathname for a given path
43+
const [locale, pathname] = await basePage.getLocaleAndPath(props);
44+
45+
// Verifies if the current route is a dynamic route
46+
const isDynamicRoute = BLOG_DYNAMIC_ROUTES.some(r => r.includes(pathname));
47+
48+
// Gets the Markdown content and context for Blog pages
49+
// otherwise this is likely a blog-category or a blog post
50+
const [content, context] = await basePage.getMarkdownContext({
51+
locale: locale,
52+
pathname: `blog/${pathname}`,
53+
});
54+
55+
// If this isn't a valid dynamic route for blog post or there's no mardown file
56+
// for this, then we fail as not found as there's nothing we can do.
57+
if (isDynamicRoute || context.filename) {
58+
return basePage.renderPage({
59+
content: content,
60+
layout: context.frontmatter.layout ?? 'blog-category',
61+
context: { ...context, pathname: `/blog/${pathname}` },
62+
});
63+
}
64+
65+
return notFound();
66+
};
67+
68+
// Enforces that this route is used as static rendering
69+
// Except whenever on the Development mode as we want instant-refresh when making changes
70+
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
71+
export const dynamic = 'force-static';
72+
73+
// Ensures that this endpoint is invalidated and re-executed every X minutes
74+
// so that when new deployments happen, the data is refreshed
75+
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
76+
export const revalidate = 300;
77+
78+
export default getPage;

apps/site/app/[locale]/next-data/page-data/route.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import { parseRichTextIntoPlainText } from '#site/util/string';
1111
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
1212
export const GET = async () => {
1313
// Retrieves all available routes for the default locale
14-
const allAvailbleRoutes = await dynamicRouter.getRoutesByLanguage(
15-
defaultLocale.code
16-
);
14+
const allAvailbleRoutes = await dynamicRouter.getAllRoutes();
1715

1816
// We exclude the blog routes from the available pages metadata
1917
// as they are generated separately and are not part of the static pages

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

Lines changed: 29 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,31 @@
1-
/**
2-
* This file contains the logic for rendering our dynamic and static routes within the Node.js Website
3-
* this page route template is used to render the entry points for each locale and then also reused within
4-
* the [...path] route to render the individual pages under each locale of the Website.
5-
*
6-
* Note: that each `page.tsx` should have its own `generateStaticParams` to prevent clash of
7-
* dynamic params, which will lead on static export errors and other sort of issues.
8-
*/
9-
10-
import { notFound, redirect } from 'next/navigation';
11-
import { setRequestLocale } from 'next-intl/server';
1+
import { notFound } from 'next/navigation';
122
import type { FC } from 'react';
133

14-
import { setClientContext } from '#site/client-context';
15-
import WithLayout from '#site/components/withLayout';
16-
import {
17-
ENABLE_STATIC_EXPORT_LOCALE,
18-
ENABLE_STATIC_EXPORT,
19-
} from '#site/next.constants.mjs';
20-
import {
21-
PAGE_VIEWPORT,
22-
DYNAMIC_ROUTES,
23-
} from '#site/next.dynamic.constants.mjs';
24-
import { dynamicRouter } from '#site/next.dynamic.mjs';
25-
import { allLocaleCodes, availableLocaleCodes } from '#site/next.locales.mjs';
4+
import { ENABLE_STATIC_EXPORT } from '#site/next.constants.mjs';
5+
import { ENABLE_STATIC_EXPORT_LOCALE } from '#site/next.constants.mjs';
6+
import * as basePage from '#site/next.dynamic.page.mjs';
7+
import { availableLocaleCodes } from '#site/next.locales.mjs';
268
import { defaultLocale } from '#site/next.locales.mjs';
27-
import { MatterProvider } from '#site/providers/matterProvider';
289

2910
type DynamicStaticPaths = { path: Array<string>; locale: string };
3011
type DynamicParams = { params: Promise<DynamicStaticPaths> };
3112

3213
// This is the default Viewport Metadata
3314
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function
34-
export const generateViewport = () => ({ ...PAGE_VIEWPORT });
15+
export const generateViewport = basePage.generateViewport;
3516

3617
// This generates each page's HTML Metadata
3718
// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata
38-
export const generateMetadata = async (props: DynamicParams) => {
39-
const { path = [], locale = defaultLocale.code } = await props.params;
40-
41-
const pathname = dynamicRouter.getPathname(path);
42-
43-
return dynamicRouter.getPageMetadata(locale, pathname);
44-
};
19+
export const generateMetadata = basePage.generateMetadata;
4520

46-
// Generates all possible static paths based on the locales and environment configuration
47-
// - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false)
48-
// - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales
49-
// - Otherwise, generates paths only for the default locale
50-
// @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params
21+
/**
22+
* Generates all possible static paths based on the locales and environment configuration
23+
* - Returns an empty array if static export is disabled (`ENABLE_STATIC_EXPORT` is false)
24+
* - If `ENABLE_STATIC_EXPORT_LOCALE` is true, generates paths for all available locales
25+
* - Otherwise, generates paths only for the default locale
26+
*
27+
* @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params
28+
*/
5129
export const generateStaticParams = async () => {
5230
// Return an empty array if static export is disabled
5331
if (!ENABLE_STATIC_EXPORT) {
@@ -61,7 +39,7 @@ export const generateStaticParams = async () => {
6139

6240
const routes = await Promise.all(
6341
// Gets all mapped routes to the Next.js Routing Engine by Locale
64-
locales.map((locale: string) => ({ locale }))
42+
locales.map(locale => ({ locale }))
6543
);
6644

6745
return routes.flat().sort();
@@ -72,87 +50,22 @@ export const generateStaticParams = async () => {
7250
// finally it returns (if the locale and route are valid) the React Component with the relevant context
7351
// and attached context providers for rendering the current page
7452
const getPage: FC<DynamicParams> = async props => {
75-
const { path = [], locale = defaultLocale.code } = await props.params;
76-
77-
if (!availableLocaleCodes.includes(locale)) {
78-
// Forces the current locale to be the Default Locale
79-
setRequestLocale(defaultLocale.code);
80-
81-
if (!allLocaleCodes.includes(locale)) {
82-
// when the locale is not listed in the locales, return NotFound
83-
return notFound();
84-
}
85-
86-
// Redirect to the default locale path
87-
const pathname = dynamicRouter.getPathname(path);
88-
89-
return redirect(`/${defaultLocale.code}/${pathname}`);
90-
}
91-
92-
// Configures the current Locale to be the given Locale of the Request
93-
setRequestLocale(locale);
94-
9553
// Gets the current full pathname for a given path
96-
const pathname = dynamicRouter.getPathname(path);
97-
98-
const staticGeneratedLayout = DYNAMIC_ROUTES.get(pathname);
54+
const [locale, pathname] = await basePage.getLocaleAndPath(props);
9955

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) {
104-
// Metadata and shared Context to be available through the lifecycle of the page
105-
const sharedContext = { pathname: `/${pathname}` };
106-
107-
// Defines a shared Server Context for the Client-Side
108-
// That is shared for all pages under the dynamic router
109-
setClientContext(sharedContext);
110-
111-
// The Matter Provider allows Client-Side injection of the data
112-
// to a shared React Client Provider even though the page is rendered
113-
// within a server-side context
114-
return (
115-
<MatterProvider {...sharedContext}>
116-
<WithLayout layout={staticGeneratedLayout} />
117-
</MatterProvider>
118-
);
119-
}
120-
121-
// We retrieve the source of the Markdown file by doing an educated guess
122-
// of what possible files could be the source of the page, since the extension
123-
// context is lost from `getStaticProps` as a limitation of Next.js itself
124-
const { source, filename } = await dynamicRouter.getMarkdownFile(
56+
// Gets the Markdown content and context
57+
const [content, context] = await basePage.getMarkdownContext({
12558
locale,
126-
pathname
127-
);
128-
129-
if (source.length && filename.length) {
130-
// This parses the source Markdown content and returns a React Component and
131-
// relevant context from the Markdown File
132-
const { content, frontmatter, headings, readingTime } =
133-
await dynamicRouter.getMDXContent(source, filename);
134-
135-
// Metadata and shared Context to be available through the lifecycle of the page
136-
const sharedContext = {
137-
frontmatter: frontmatter,
138-
headings: headings,
139-
pathname: `/${pathname}`,
140-
readingTime: readingTime,
141-
filename: filename,
142-
};
143-
144-
// Defines a shared Server Context for the Client-Side
145-
// That is shared for all pages under the dynamic router
146-
setClientContext(sharedContext);
147-
148-
// The Matter Provider allows Client-Side injection of the data
149-
// to a shared React Client Provider even though the page is rendered
150-
// within a server-side context
151-
return (
152-
<MatterProvider {...sharedContext}>
153-
<WithLayout layout={frontmatter.layout}>{content}</WithLayout>
154-
</MatterProvider>
155-
);
59+
pathname,
60+
});
61+
62+
// If we have a filename and layout then we have a page
63+
if (context.filename && context.frontmatter.layout) {
64+
return basePage.renderPage({
65+
content: content,
66+
layout: context.frontmatter.layout,
67+
context: context,
68+
});
15669
}
15770

15871
return notFound();

0 commit comments

Comments
 (0)