Skip to content
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

meta: refactor data generation to server-side only #6137

Merged
merged 6 commits into from
Nov 24, 2023
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
4 changes: 0 additions & 4 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ node_modules
.swc
build

# Public Files
public/node-releases-data.json
public/blog-posts-data.json

# We don't want to lint/prettify the Coverage Results
coverage
junit.xml
Expand Down
4 changes: 0 additions & 4 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# if the generated files got tracked to this commit we revert them
git reset public/node-releases-data.json
git reset public/blog-posts-data.json

# lint and format staged files
npx lint-staged
2 changes: 0 additions & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ build

# Next.js Generated Files
public/static/documents
public/node-releases-data.json
public/blog-posts-data.json

# Jest
coverage
Expand Down
15 changes: 11 additions & 4 deletions app/[locale]/[[...path]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import type { FC } from 'react';
import { setClientContext } from '@/client-context';
import { MDXRenderer } from '@/components/mdxRenderer';
import { WithLayout } from '@/components/withLayout';
import { DEFAULT_VIEWPORT, ENABLE_STATIC_EXPORT } from '@/next.constants.mjs';
import { ENABLE_STATIC_EXPORT } from '@/next.constants.mjs';
import { DEFAULT_VIEWPORT } from '@/next.dynamic.constants.mjs';
import { dynamicRouter } from '@/next.dynamic.mjs';
import { availableLocaleCodes, defaultLocale } from '@/next.locales.mjs';
import { MatterProvider } from '@/providers/matterProvider';
Expand All @@ -14,8 +15,8 @@ type DynamicStaticPaths = { path: string[]; locale: string };
type DynamicParams = { params: DynamicStaticPaths };

// This is the default Viewport Metadata
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport
export const viewport = DEFAULT_VIEWPORT;
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function
export const generateViewport = async () => ({ ...DEFAULT_VIEWPORT });

// This generates each page's HTML Metadata
// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata
Expand Down Expand Up @@ -113,7 +114,13 @@ const getPage: FC<DynamicParams> = async ({ params }) => {
return notFound();
};

// Enforce that all these routes are compatible with SSR
// In this case we want to catch-all possible pages even to this page. This ensures that we use our 404
// and that all pages including existing ones are handled here and provide `next-intl` locale also
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams
export const dynamicParams = true;

// Enforces that this route is used as static rendering
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'error';

export default getPage;
46 changes: 24 additions & 22 deletions app/[locale]/feed/[feed]/route.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
import { NextResponse } from 'next/server';

import { generateWebsiteFeeds } from '@/next.data.mjs';
import { blogData } from '@/next.json.mjs';
import provideWebsiteFeeds from '@/next-data/providers/websiteFeeds';
import { siteConfig } from '@/next.json.mjs';
import { defaultLocale } from '@/next.locales.mjs';

// loads all the data from the blog-posts-data.json file
const websiteFeeds = generateWebsiteFeeds(blogData);
// We only support fetching these pages from the /en/ locale code
const locale = defaultLocale.code;

type StaticParams = { params: { feed: string } };
type StaticParams = { params: { feed: string; locale: string } };

// This is the Route Handler for the `GET` method which handles the request
// for Blog Feeds within the Node.js Website
// for the Node.js Website Blog Feeds (RSS)
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
export const GET = (_: Request, { params }: StaticParams) => {
if (params.feed.includes('.xml') && websiteFeeds.has(params.feed)) {
return new NextResponse(websiteFeeds.get(params.feed)?.rss2(), {
headers: { 'Content-Type': 'application/xml' },
});
}

return new NextResponse(null, { status: 404 });
export const GET = async (_: Request, { params }: StaticParams) => {
// Generate the Feed for the given feed type (blog, releases, etc)
const websiteFeed = await provideWebsiteFeeds(params.feed);

return new NextResponse(websiteFeed, {
headers: { 'Content-Type': 'application/xml' },
status: websiteFeed ? 200 : 404,
});
};

// This function generates the static paths that come from the dynamic segments
// `en/feeds/[feed]` and returns an array of all available static paths
// this is useful for static exports, for example.
// Note that differently from the App Router these don't get built at the build time
// only if the export is already set for static export
export const generateStaticParams = () =>
[...websiteFeeds.keys()].map(feed => ({ feed, locale: defaultLocale.code }));

// Enforces that this route is used as static rendering
// `[locale]/feeds/[feed]` and returns an array of all available static paths
// This is used for ISR static validation and generation
export const generateStaticParams = async () =>
siteConfig.rssFeeds.map(feed => ({ feed: feed.file, locale }));

// Forces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams
export const dynamicParams = false;

// Enforces that this route is cached and static as much as possible
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'error';
42 changes: 42 additions & 0 deletions app/[locale]/next-data/blog-data/[category]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import provideBlogData from '@/next-data/providers/blogData';
import { defaultLocale } from '@/next.locales.mjs';

// We only support fetching these pages from the /en/ locale code
const locale = defaultLocale.code;

type StaticParams = { params: { category: string; locale: string } };

// This is the Route Handler for the `GET` method which handles the request
// for providing Blog Posts, Pagination for every supported Blog Category
// this includes the `year-XXXX` categories for yearly archives (pagination)
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
export const GET = async (_: Request, { params }: StaticParams) => {
const { posts, pagination } = await provideBlogData(params.category);

return Response.json(
{ posts, pagination },
{ status: posts.length ? 200 : 404 }
);
};

// This function generates the static paths that come from the dynamic segments
// `[locale]/next-data/blog-data/[category]` and returns an array of all available static paths
// This is used for ISR static validation and generation
export const generateStaticParams = async () => {
// This metadata is the original list of all available categories and all available years
// within the Node.js Website Blog Posts (2011, 2012...)
const { meta } = await provideBlogData();

return [
...meta.categories.map(category => ({ category, locale })),
...meta.pagination.map(year => ({ category: `year-${year}`, locale })),
];
};

// Forces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams
export const dynamicParams = false;

// Enforces that this route is cached and static as much as possible
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'error';
27 changes: 27 additions & 0 deletions app/[locale]/next-data/release-data/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import provideReleaseData from '@/next-data/providers/releaseData';
import { defaultLocale } from '@/next.locales.mjs';

// We only support fetching these pages from the /en/ locale code
const locale = defaultLocale.code;

// This is the Route Handler for the `GET` method which handles the request
// for generating static data related to the Node.js Release Data
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
export const GET = async () => {
const releaseData = await provideReleaseData();

return Response.json(releaseData);
};

// This function generates the static paths that come from the dynamic segments
// `[locale]/next-data/release-data/` and returns an array of all available static paths
// This is used for ISR static validation and generation
export const generateStaticParams = async () => [{ locale }];

// Forces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams
export const dynamicParams = false;

// Enforces that this route is used as static rendering
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
export const dynamic = 'error';
7 changes: 3 additions & 4 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
const paths: string[] = [];

for (const locale of availableLocaleCodes) {
const routesForLanguage = await dynamicRouter.getRoutesByLanguage(locale);
paths.push(
...routesForLanguage.map(route => `${baseUrlAndPath}/${locale}/${route}`)
);
const routes = await dynamicRouter.getRoutesByLanguage(locale);

paths.push(...routes.map(route => `${baseUrlAndPath}/${locale}/${route}`));
}

const currentDate = new Date().toISOString();
Expand Down
11 changes: 9 additions & 2 deletions components/Docs/NodeApiVersionLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { FC } from 'react';

import getReleaseData from '@/next-data/releaseData';
import { DOCS_URL } from '@/next.constants.mjs';
import { releaseData } from '@/next.json.mjs';

const NodeApiVersionLinks = () => {
// This is a React Async Server Component
// Note that Hooks cannot be used in a RSC async component
// Async Components do not get re-rendered at all.
const NodeApiVersionLinks: FC = async () => {
const releaseData = await getReleaseData();

// Gets all major releases without the 0x release as those are divided on 0.12x and 0.10x
const mappedReleases = releaseData.slice(0, -1).map(({ major }) => (
<li key={major}>
Expand Down
13 changes: 9 additions & 4 deletions components/Downloads/DownloadReleasesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import type { FC } from 'react';

import { releaseData } from '@/next.json.mjs';
import getReleaseData from '@/next-data/releaseData';
import { getNodeApiLink } from '@/util/getNodeApiLink';
import { getNodejsChangelog } from '@/util/getNodeJsChangelog';

const DownloadReleasesTable: FC = () => {
const t = useTranslations();
// This is a React Async Server Component
// Note that Hooks cannot be used in a RSC async component
// Async Components do not get re-rendered at all.
const DownloadReleasesTable: FC = async () => {
const releaseData = await getReleaseData();

const t = await getTranslations();

return (
<table id="tbVersions" className="download-table full-width">
Expand Down
18 changes: 3 additions & 15 deletions components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@
import Image from 'next/image';
import { useTranslations } from 'next-intl';
import { useTheme } from 'next-themes';
import type { FC, PropsWithChildren } from 'react';
import { useState } from 'react';

import ActiveLink from '@/components/Common/ActiveLink';
import Link from '@/components/Link';
import { useSiteNavigation } from '@/hooks';
import { usePathname } from '@/navigation.mjs';
import { BASE_PATH } from '@/next.constants.mjs';
import { availableLocales } from '@/next.locales.mjs';

const Header = () => {
const { navigationItems } = useSiteNavigation();
const Header: FC<PropsWithChildren> = ({ children }) => {
const [showLangPicker, setShowLangPicker] = useState(false);
const { resolvedTheme, setTheme } = useTheme();

Expand All @@ -36,17 +34,7 @@ const Header = () => {
/>
</Link>

<nav aria-label="primary">
<ul className="list-divider-pipe">
{navigationItems.map((item, key) => (
<li key={key}>
<ActiveLink href={item.link} allowSubPath>
{item.text}
</ActiveLink>
</li>
))}
</ul>
</nav>
{children}

<div className="switchers">
<button
Expand Down
2 changes: 1 addition & 1 deletion components/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { FC } from 'react';

import Link from '@/components/Link';

type PaginationProps = { prev?: number; next?: number };
type PaginationProps = { prev?: number | null; next?: number | null };

const Pagination: FC<PaginationProps> = ({ next, prev }) => {
const t = useTranslations();
Expand Down
24 changes: 24 additions & 0 deletions components/TopNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { FC } from 'react';

import ActiveLink from '@/components/Common/ActiveLink';
import { useSiteNavigation } from '@/hooks/server';

const TopNavigation: FC = () => {
const { navigationItems } = useSiteNavigation();

return (
<nav aria-label="primary">
<ul className="list-divider-pipe">
{navigationItems.map(({ link, text }) => (
<li key={link}>
<ActiveLink href={link} allowSubPath>
{text}
</ActiveLink>
</li>
))}
</ul>
</nav>
);
};

export default TopNavigation;
9 changes: 7 additions & 2 deletions components/withNodeRelease.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import type { FC } from 'react';

import { releaseData } from '@/next.json.mjs';
import getReleaseData from '@/next-data/releaseData';
import type { NodeRelease, NodeReleaseStatus } from '@/types';

type WithNodeReleaseProps = {
status: NodeReleaseStatus[] | NodeReleaseStatus;
children: FC<{ release: NodeRelease }>;
};

export const WithNodeRelease: FC<WithNodeReleaseProps> = ({
// This is a React Async Server Component
// Note that Hooks cannot be used in a RSC async component
// Async Components do not get re-rendered at all.
export const WithNodeRelease: FC<WithNodeReleaseProps> = async ({
status,
children: Component,
}) => {
const releaseData = await getReleaseData();

const matchingRelease = releaseData.find(release =>
[status].flat().includes(release.status)
);
Expand Down
1 change: 0 additions & 1 deletion hooks/react-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ export { default as useDetectOS } from './useDetectOS';
export { default as useMediaQuery } from './useMediaQuery';
export { default as useNotification } from './useNotification';
export { default as useClientContext } from './useClientContext';
export { default as useBlogData } from './useBlogData';
export { default as useSiteNavigation } from './useSiteNavigation';
13 changes: 0 additions & 13 deletions hooks/react-client/useBlogData.ts

This file was deleted.

6 changes: 4 additions & 2 deletions hooks/react-client/useSiteNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import useBaseSiteNavigation from '@/hooks/useBaseSiteNavigation';
const useSiteNavigation = () => {
throw new Error('Attempted to call useSiteNavigation from RCC');
bmuenzenmeyer marked this conversation as resolved.
Show resolved Hide resolved
};

export default useBaseSiteNavigation;
export default useSiteNavigation;
1 change: 0 additions & 1 deletion hooks/react-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ export { default as useDetectOS } from './useDetectOS';
export { default as useMediaQuery } from './useMediaQuery';
export { default as useNotification } from './useNotification';
export { default as useClientContext } from './useClientContext';
export { default as useBlogData } from './useBlogData';
export { default as useSiteNavigation } from './useSiteNavigation';
11 changes: 0 additions & 11 deletions hooks/react-server/useBlogData.ts

This file was deleted.

Loading
Loading