diff --git a/.eslintignore b/.eslintignore index 16969c5035f7d..f8dbaece5a6f9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/.husky/pre-commit b/.husky/pre-commit index 7c3e68ae4cd34..674ab67d4f451 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -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 diff --git a/.prettierignore b/.prettierignore index 35a26f341b4ba..513b005d5c368 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,8 +9,6 @@ build # Next.js Generated Files public/static/documents -public/node-releases-data.json -public/blog-posts-data.json # Jest coverage diff --git a/app/[locale]/[[...path]]/page.tsx b/app/[locale]/[[...path]]/page.tsx index f5d5b1fa9649e..34f9e544f7402 100644 --- a/app/[locale]/[[...path]]/page.tsx +++ b/app/[locale]/[[...path]]/page.tsx @@ -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'; @@ -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 @@ -113,7 +114,13 @@ const getPage: FC = 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; diff --git a/app/[locale]/feed/[feed]/route.ts b/app/[locale]/feed/[feed]/route.ts index 094b1112e3bc4..1bd00db34586b 100644 --- a/app/[locale]/feed/[feed]/route.ts +++ b/app/[locale]/feed/[feed]/route.ts @@ -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'; diff --git a/app/[locale]/next-data/blog-data/[category]/route.ts b/app/[locale]/next-data/blog-data/[category]/route.ts new file mode 100644 index 0000000000000..1c99211c46696 --- /dev/null +++ b/app/[locale]/next-data/blog-data/[category]/route.ts @@ -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'; diff --git a/app/[locale]/next-data/release-data/route.ts b/app/[locale]/next-data/release-data/route.ts new file mode 100644 index 0000000000000..f8026bcc0adbf --- /dev/null +++ b/app/[locale]/next-data/release-data/route.ts @@ -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'; diff --git a/app/sitemap.ts b/app/sitemap.ts index 50a67cc9c9ba4..35d65c0e12860 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -18,10 +18,9 @@ const sitemap = async (): Promise => { 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(); diff --git a/components/Docs/NodeApiVersionLinks.tsx b/components/Docs/NodeApiVersionLinks.tsx index 5ac9f7d7ade54..7ac1448c8523e 100644 --- a/components/Docs/NodeApiVersionLinks.tsx +++ b/components/Docs/NodeApiVersionLinks.tsx @@ -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 }) => (
  • diff --git a/components/Downloads/DownloadReleasesTable.tsx b/components/Downloads/DownloadReleasesTable.tsx index 5610d14e811b6..cd445350a6004 100644 --- a/components/Downloads/DownloadReleasesTable.tsx +++ b/components/Downloads/DownloadReleasesTable.tsx @@ -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 ( diff --git a/components/Header.tsx b/components/Header.tsx index d095117d4dcc8..846d03c3f7f40 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -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 = ({ children }) => { const [showLangPicker, setShowLangPicker] = useState(false); const { resolvedTheme, setTheme } = useTheme(); @@ -36,17 +34,7 @@ const Header = () => { /> - + {children}