diff --git a/app/[locale]/feed/[feed]/route.ts b/app/[locale]/feed/[feed]/route.ts index 6e331a9e3c30e..4007bb97e210e 100644 --- a/app/[locale]/feed/[feed]/route.ts +++ b/app/[locale]/feed/[feed]/route.ts @@ -1,21 +1,24 @@ import { NextResponse } from 'next/server'; -import { websiteFeeds } from '@/next.data.mjs'; +import provideWebsiteFeeds from '@/next-data/providers/websiteFeeds'; +import { siteConfig } from '@/next.json.mjs'; import { defaultLocale } from '@/next.locales.mjs'; -type StaticParams = { params: { feed: string } }; +// We only support fetching these pages from the /en/ locale code +const locale = defaultLocale.code; + +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 // @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' }, - }); - } +export const GET = async (_: Request, { params }: StaticParams) => { + const websiteFeed = await provideWebsiteFeeds(params.feed); - return new NextResponse(null, { status: 404 }); + 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 @@ -23,8 +26,8 @@ export const GET = (_: Request, { params }: StaticParams) => { // 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 })); +export const generateStaticParams = async () => + siteConfig.rssFeeds.map(feed => ({ feed: feed.file, locale })); // Enforces that this route is used as static rendering // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic 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..f7893f76d3082 --- /dev/null +++ b/app/[locale]/next-data/blog-data/[category]/route.ts @@ -0,0 +1,39 @@ +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 generating our static data for the Node.js Website +// @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 +// `en/next-data/[type]` 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 = async () => { + const { + meta: { categories, pagination }, + } = await provideBlogData(); + + return [ + ...categories.map(category => ({ category, locale })), + ...pagination.map(year => ({ category: `year-${year}`, locale })), + ]; +}; + +// 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/[locale]/next-data/release-data/route.ts b/app/[locale]/next-data/release-data/route.ts new file mode 100644 index 0000000000000..dce567a5ab71b --- /dev/null +++ b/app/[locale]/next-data/release-data/route.ts @@ -0,0 +1,25 @@ +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 our static data for the Node.js Website +// @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 +// `en/next-data/[type]` 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 = async () => [{ locale }]; + +// 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/components/Docs/NodeApiVersionLinks.tsx b/components/Docs/NodeApiVersionLinks.tsx index f5bcf42b83882..a9039a496e7b2 100644 --- a/components/Docs/NodeApiVersionLinks.tsx +++ b/components/Docs/NodeApiVersionLinks.tsx @@ -1,7 +1,9 @@ +import getReleaseData from '@/next-data/releaseData'; import { DOCS_URL } from '@/next.constants.mjs'; -import { releaseData } from '@/next.data.mjs'; -const NodeApiVersionLinks = () => { +const NodeApiVersionLinks = 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 bcb1a1f507357..45e5ed64c0d84 100644 --- a/components/Downloads/DownloadReleasesTable.tsx +++ b/components/Downloads/DownloadReleasesTable.tsx @@ -1,12 +1,14 @@ -import { useTranslations } from 'next-intl'; +import { getTranslations } from 'next-intl/server'; import type { FC } from 'react'; -import { releaseData } from '@/next.data.mjs'; +import getReleaseData from '@/next-data/releaseData'; import { getNodeApiLink } from '@/util/getNodeApiLink'; import { getNodejsChangelog } from '@/util/getNodeJsChangelog'; -const DownloadReleasesTable: FC = () => { - const t = useTranslations(); +const DownloadReleasesTable: FC = async () => { + const releaseData = await getReleaseData(); + + const t = await getTranslations(); return ( diff --git a/components/Pagination.tsx b/components/Pagination.tsx index fd99fa1926a6d..55f935cf7614f 100644 --- a/components/Pagination.tsx +++ b/components/Pagination.tsx @@ -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 = ({ next, prev }) => { const t = useTranslations(); diff --git a/components/withNodeRelease.tsx b/components/withNodeRelease.tsx index 82527ad8e68e5..26d945b11c31a 100644 --- a/components/withNodeRelease.tsx +++ b/components/withNodeRelease.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react'; -import { releaseData } from '@/next.data.mjs'; +import getReleaseData from '@/next-data/releaseData'; import type { NodeRelease, NodeReleaseStatus } from '@/types'; type WithNodeReleaseProps = { @@ -8,10 +8,12 @@ type WithNodeReleaseProps = { children: FC<{ release: NodeRelease }>; }; -export const WithNodeRelease: FC = ({ +export const WithNodeRelease: FC = async ({ status, children: Component, }) => { + const releaseData = await getReleaseData(); + const matchingRelease = releaseData.find(release => [status].flat().includes(release.status) ); diff --git a/hooks/react-client/index.ts b/hooks/react-client/index.ts index c9e4bd771af87..d1f35f867e99f 100644 --- a/hooks/react-client/index.ts +++ b/hooks/react-client/index.ts @@ -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'; diff --git a/hooks/react-client/useBlogData.ts b/hooks/react-client/useBlogData.ts deleted file mode 100644 index 69066a0932d55..0000000000000 --- a/hooks/react-client/useBlogData.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; - -const useBlogData = () => { - throw new Error('Attempted to call useBlogData from RCC'); -}; - -export default useBlogData; diff --git a/hooks/react-server/index.ts b/hooks/react-server/index.ts index c9e4bd771af87..d1f35f867e99f 100644 --- a/hooks/react-server/index.ts +++ b/hooks/react-server/index.ts @@ -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'; diff --git a/hooks/react-server/useBlogData.ts b/hooks/react-server/useBlogData.ts deleted file mode 100644 index f10b5fc7c3f5a..0000000000000 --- a/hooks/react-server/useBlogData.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useMemo } from 'react'; - -import useClientContext from '@/hooks/react-server/useClientContext'; -import { blogData } from '@/next.data.mjs'; - -const useBlogData = () => { - const { pathname } = useClientContext(); - - const getPostsByCategory = (category: string) => - blogData.posts.filter(post => post.category === category); - - const getPostsByYear = (year: string) => - blogData.posts.filter( - post => new Date(post.date).getFullYear() === Number(year) - ); - - const getPagination = (currentYear: number | string) => { - const _currentYear = Number(currentYear); - - return { - next: blogData.pagination.includes(_currentYear + 1) - ? _currentYear + 1 - : undefined, - prev: blogData.pagination.includes(_currentYear - 1) - ? _currentYear - 1 - : undefined, - }; - }; - - const currentCategory = useMemo(() => { - // We split the pathname to retrieve the blog category from it since the - // URL is usually blog/{category} the second path piece is usually the - // category name - const [_pathname, category] = pathname.split('/'); - - if (_pathname === 'blog' && category && category.length) { - return category; - } - - // if either the pathname does not match to a blog page - // which should not happen (as this hook should only be used in blog pages) - // or if there is no category in the URL we return the current year as category name - // which is always the default category (for example, the blog index) - return `year-${new Date().getFullYear()}`; - }, [pathname]); - - return { - posts: blogData.posts, - categories: blogData.categories, - currentCategory, - getPostsByCategory, - getPostsByYear, - getPagination, - }; -}; - -export default useBlogData; diff --git a/layouts/BlogCategoryLayout.tsx b/layouts/BlogCategoryLayout.tsx index a08b9d49d7e2f..e340620e1cec8 100644 --- a/layouts/BlogCategoryLayout.tsx +++ b/layouts/BlogCategoryLayout.tsx @@ -1,53 +1,50 @@ -import { useTranslations } from 'next-intl'; -import { useMemo } from 'react'; +import { getTranslations } from 'next-intl/server'; import type { FC } from 'react'; +import { getClientContext } from '@/client-context'; import { Time } from '@/components/Common/Time'; import Link from '@/components/Link'; import Pagination from '@/components/Pagination'; -import { useClientContext, useBlogData } from '@/hooks/server'; -import type { BlogPost } from '@/types'; +import getBlogData from '@/next-data/blogData'; -const BlogCategoryLayout: FC = () => { - const t = useTranslations(); - const { getPagination, getPostsByYear, getPostsByCategory, currentCategory } = - useBlogData(); +const getCurrentCategory = (pathname: string) => { + // We split the pathname to retrieve the blog category from it since the + // URL is usually blog/{category} the second path piece is usually the + // category name + const [_pathname, category] = pathname.split('/'); - const { frontmatter } = useClientContext(); + if (_pathname === 'blog' && category && category.length) { + return category; + } - const { posts, pagination, title } = useMemo(() => { - if (currentCategory.startsWith('year-')) { - const categoryWithoutPrefix = currentCategory.replace('year-', ''); + // if either the pathname does not match to a blog page + // which should not happen (as this hook should only be used in blog pages) + // or if there is no category in the URL we return the current year as category name + // which is always the default category (for example, the blog index) + return `year-${new Date().getFullYear()}`; +}; + +const BlogCategoryLayout: FC = async () => { + const { frontmatter, pathname } = getClientContext(); + const category = getCurrentCategory(pathname); + + const t = await getTranslations(); + + const { posts, pagination } = await getBlogData(category); - return { - posts: getPostsByYear(categoryWithoutPrefix), - pagination: getPagination(categoryWithoutPrefix), - title: t('layouts.blogIndex.currentYear', { - year: categoryWithoutPrefix, - }), - }; - } + // this only applies if current category is a year category + const year = category.replace('year-', ''); - return { - posts: getPostsByCategory(currentCategory), - pagination: undefined, - title: frontmatter.title, - }; - }, [ - currentCategory, - frontmatter.title, - getPagination, - getPostsByCategory, - getPostsByYear, - t, - ]); + const title = category.startsWith('year-') + ? t('layouts.blogIndex.currentYear', { year }) + : frontmatter.title; return (

    {title}

      - {posts.map((post: BlogPost) => ( + {posts.map(post => (