diff --git a/apps/site/app/[locale]/download/archive/[version]/page.tsx b/apps/site/app/[locale]/download/archive/[version]/page.tsx index f8b627977bab3..2fa7b6040c0cd 100644 --- a/apps/site/app/[locale]/download/archive/[version]/page.tsx +++ b/apps/site/app/[locale]/download/archive/[version]/page.tsx @@ -46,7 +46,7 @@ const getPage: FC = async props => { const [locale, pathname] = basePage.getLocaleAndPath(version, routeLocale); if (version === 'current') { - const releaseData = provideReleaseData(); + const releaseData = await provideReleaseData(); const release = releaseData.find(release => release.status === 'Current'); diff --git a/apps/site/app/[locale]/not-found.tsx b/apps/site/app/[locale]/not-found.tsx index 0842df54d486e..bca476307ac22 100644 --- a/apps/site/app/[locale]/not-found.tsx +++ b/apps/site/app/[locale]/not-found.tsx @@ -1,15 +1,15 @@ -'use client'; +'use server'; import { ArrowRightIcon } from '@heroicons/react/24/solid'; -import { useTranslations } from 'next-intl'; +import { getTranslations } from 'next-intl/server'; import type { FC } from 'react'; import Button from '#site/components/Common/Button'; import Turtle from '#site/components/Common/Turtle'; import GlowingBackdropLayout from '#site/layouts/GlowingBackdrop'; -const NotFoundPage: FC = () => { - const t = useTranslations(); +const NotFoundPage: FC = async () => { + const t = await getTranslations(); return ( diff --git a/apps/site/components/EOL/EOLReleaseTable.tsx b/apps/site/components/EOL/EOLReleaseTable.tsx deleted file mode 100644 index 2119fefb8ef03..0000000000000 --- a/apps/site/components/EOL/EOLReleaseTable.tsx +++ /dev/null @@ -1,83 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { Fragment, useState } from 'react'; -import type { FC } from 'react'; - -import FormattedTime from '#site/components/Common/FormattedTime'; -import LinkWithArrow from '#site/components/Common/LinkWithArrow'; -import EOLModal from '#site/components/EOL/EOLModal'; -import VulnerabilityChips from '#site/components/EOL/VulnerabilityChips'; -import provideReleaseData from '#site/next-data/providers/releaseData'; -import provideVulnerabilities from '#site/next-data/providers/vulnerabilities'; -import { EOL_VERSION_IDENTIFIER } from '#site/next.constants.mjs'; - -const EOLReleaseTable: FC = () => { - const releaseData = provideReleaseData(); - const vulnerabilities = provideVulnerabilities(); - - const eolReleases = releaseData.filter( - release => release.status === EOL_VERSION_IDENTIFIER - ); - - const t = useTranslations(); - - const [currentModal, setCurrentModal] = useState(); - - return ( - - - - - - - - - - - - {eolReleases.map(release => ( - - - - - - - - - - - - open || setCurrentModal(undefined)} - /> - - ))} - -
- {t('components.eolTable.version')} ( - {t('components.eolTable.codename')}) - {t('components.eolTable.lastUpdated')}{t('components.eolTable.vulnerabilities')}{t('components.eolTable.details')}
- v{release.major}{' '} - {release.codename ? `(${release.codename})` : ''} - - - - - - setCurrentModal(release.version)} - > - {t('components.downloadReleasesTable.details')} - -
- ); -}; - -export default EOLReleaseTable; diff --git a/apps/site/components/EOL/EOLReleaseTable/TableBody.tsx b/apps/site/components/EOL/EOLReleaseTable/TableBody.tsx new file mode 100644 index 0000000000000..ee2fa061f37c7 --- /dev/null +++ b/apps/site/components/EOL/EOLReleaseTable/TableBody.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; +import { Fragment, useState } from 'react'; + +import FormattedTime from '#site/components/Common/FormattedTime'; +import LinkWithArrow from '#site/components/Common/LinkWithArrow'; +import EOLModal from '#site/components/EOL/EOLModal'; +import VulnerabilityChips from '#site/components/EOL/VulnerabilityChips'; +import type { NodeRelease } from '#site/types/releases.js'; +import type { GroupedVulnerabilities } from '#site/types/vulnerabilities.js'; + +type EOLReleaseTableBodyProps = { + eolReleases: Array; + vulnerabilities: GroupedVulnerabilities; +}; + +const EOLReleaseTableBody: FC = ({ + eolReleases, + vulnerabilities, +}) => { + const t = useTranslations(); + + const [currentModal, setCurrentModal] = useState(); + + return ( + + {eolReleases.map(release => ( + + + + v{release.major} {release.codename ? `(${release.codename})` : ''} + + + + + + + + + + + + setCurrentModal(release.version)} + > + {t('components.downloadReleasesTable.details')} + + + + + open || setCurrentModal(undefined)} + /> + + ))} + + ); +}; + +export default EOLReleaseTableBody; diff --git a/apps/site/components/EOL/EOLReleaseTable/index.tsx b/apps/site/components/EOL/EOLReleaseTable/index.tsx new file mode 100644 index 0000000000000..36ee147bf5f7c --- /dev/null +++ b/apps/site/components/EOL/EOLReleaseTable/index.tsx @@ -0,0 +1,42 @@ +import { getTranslations } from 'next-intl/server'; +import type { FC } from 'react'; + +import provideReleaseData from '#site/next-data/providers/releaseData'; +import provideVulnerabilities from '#site/next-data/providers/vulnerabilities'; +import { EOL_VERSION_IDENTIFIER } from '#site/next.constants.mjs'; + +import EOLReleaseTableBody from './TableBody'; + +const EOLReleaseTable: FC = async () => { + const releaseData = await provideReleaseData(); + const vulnerabilities = provideVulnerabilities(); + + const eolReleases = releaseData.filter( + release => release.status === EOL_VERSION_IDENTIFIER + ); + + const t = await getTranslations(); + + return ( + + + + + + + + + + + +
+ {t('components.eolTable.version')} ( + {t('components.eolTable.codename')}) + {t('components.eolTable.lastUpdated')}{t('components.eolTable.vulnerabilities')}{t('components.eolTable.details')}
+ ); +}; + +export default EOLReleaseTable; diff --git a/apps/site/components/Releases/PreviousReleasesTable.tsx b/apps/site/components/Releases/PreviousReleasesTable.tsx deleted file mode 100644 index 58f7846f80fd2..0000000000000 --- a/apps/site/components/Releases/PreviousReleasesTable.tsx +++ /dev/null @@ -1,98 +0,0 @@ -'use client'; - -import Badge from '@node-core/ui-components/Common/Badge'; -import { useTranslations } from 'next-intl'; -import type { FC } from 'react'; -import { Fragment, useState } from 'react'; - -import FormattedTime from '#site/components/Common/FormattedTime'; -import LinkWithArrow from '#site/components/Common/LinkWithArrow'; -import Link from '#site/components/Link'; -import provideReleaseData from '#site/next-data/providers/releaseData'; - -import ReleaseModal from './ReleaseModal'; - -const BADGE_KIND_MAP = { - 'End-of-life': 'warning', - 'Maintenance LTS': 'neutral', - 'Active LTS': 'info', - Current: 'default', - Pending: 'default', -} as const; - -const PreviousReleasesTable: FC = () => { - const releaseData = provideReleaseData(); - - const t = useTranslations(); - - const [currentModal, setCurrentModal] = useState(); - - return ( - - - - - - - - - - - - - - {releaseData.map(release => ( - - - - - - - - - - - - - - - - open || setCurrentModal(undefined)} - /> - - ))} - -
{t('components.downloadReleasesTable.version')}{t('components.downloadReleasesTable.codename')}{t('components.downloadReleasesTable.firstReleased')}{t('components.downloadReleasesTable.lastUpdated')}{t('components.downloadReleasesTable.status')}
- - v{release.major} - - - {release.codename || '-'} - - - - - - - {release.status} - {release.status === 'End-of-life' ? ' (EoL)' : ''} - - - setCurrentModal(release.version)} - > - {t('components.downloadReleasesTable.details')} - -
- ); -}; - -export default PreviousReleasesTable; diff --git a/apps/site/components/Releases/PreviousReleasesTable/TableBody.tsx b/apps/site/components/Releases/PreviousReleasesTable/TableBody.tsx new file mode 100644 index 0000000000000..f5011cac7290d --- /dev/null +++ b/apps/site/components/Releases/PreviousReleasesTable/TableBody.tsx @@ -0,0 +1,87 @@ +'use client'; + +import Badge from '@node-core/ui-components/Common/Badge'; +import { useTranslations } from 'next-intl'; +import type { FC } from 'react'; +import { Fragment, useState } from 'react'; + +import FormattedTime from '#site/components/Common/FormattedTime'; +import LinkWithArrow from '#site/components/Common/LinkWithArrow'; +import Link from '#site/components/Link'; +import type { NodeRelease } from '#site/types/releases.js'; + +import ReleaseModal from '../ReleaseModal'; + +const BADGE_KIND_MAP = { + 'End-of-life': 'warning', + 'Maintenance LTS': 'neutral', + 'Active LTS': 'info', + Current: 'default', + Pending: 'default', +} as const; + +type PreviousReleasesTableBodyProps = { + releaseData: Array; +}; + +const PreviousReleasesTableBody: FC = ({ + releaseData, +}) => { + const t = useTranslations(); + + const [currentModal, setCurrentModal] = useState(); + + return ( + + {releaseData.map(release => ( + + + + + v{release.major} + + + + + {release.codename || '-'} + + + + + + + + + + + + + {release.status} + {release.status === 'End-of-life' ? ' (EoL)' : ''} + + + + + setCurrentModal(release.version)} + > + {t('components.downloadReleasesTable.details')} + + + + + open || setCurrentModal(undefined)} + /> + + ))} + + ); +}; + +export default PreviousReleasesTableBody; diff --git a/apps/site/components/Releases/PreviousReleasesTable/index.tsx b/apps/site/components/Releases/PreviousReleasesTable/index.tsx new file mode 100644 index 0000000000000..342528ec62e32 --- /dev/null +++ b/apps/site/components/Releases/PreviousReleasesTable/index.tsx @@ -0,0 +1,31 @@ +import { getTranslations } from 'next-intl/server'; +import type { FC } from 'react'; + +import provideReleaseData from '#site/next-data/providers/releaseData'; + +import PreviousReleasesTableBody from './TableBody'; + +const PreviousReleasesTable: FC = async () => { + const releaseData = await provideReleaseData(); + + const t = await getTranslations(); + + return ( + + + + + + + + + + + + + +
{t('components.downloadReleasesTable.version')}{t('components.downloadReleasesTable.codename')}{t('components.downloadReleasesTable.firstReleased')}{t('components.downloadReleasesTable.lastUpdated')}{t('components.downloadReleasesTable.status')}
+ ); +}; + +export default PreviousReleasesTable; diff --git a/apps/site/components/withDownloadArchive.tsx b/apps/site/components/withDownloadArchive.tsx index 460150ead3948..df8ecdb70aea0 100644 --- a/apps/site/components/withDownloadArchive.tsx +++ b/apps/site/components/withDownloadArchive.tsx @@ -27,7 +27,7 @@ const WithDownloadArchive: FC = async ({ const version = extractVersionFromPath(pathname); // Find the release data for the given version - const releaseData = provideReleaseData(); + const releaseData = await provideReleaseData(); const release = releaseData.find(release => // Match major version only (e.g., v22.x.x for release.major v22) version.startsWith(`v${release.major}`) diff --git a/apps/site/components/withDownloadSection.tsx b/apps/site/components/withDownloadSection.tsx index 34d76d09d6991..3caa60d024598 100644 --- a/apps/site/components/withDownloadSection.tsx +++ b/apps/site/components/withDownloadSection.tsx @@ -4,20 +4,27 @@ import type { FC, PropsWithChildren } from 'react'; import { getClientContext } from '#site/client-context'; import WithNodeRelease from '#site/components/withNodeRelease'; import provideDownloadSnippets from '#site/next-data/providers/downloadSnippets'; -import provideReleaseData from '#site/next-data/providers/releaseData'; import { defaultLocale } from '#site/next.locales.mjs'; import { ReleaseProvider, ReleasesProvider, } from '#site/providers/releaseProvider'; +import type { NodeRelease } from '../types'; + // By default the translated languages do not contain all the download snippets // Hence we always merge any translated snippet with the fallbacks for missing snippets const fallbackSnippets = provideDownloadSnippets(defaultLocale.code); -const WithDownloadSection: FC = ({ children }) => { +type WithDownloadSectionProps = PropsWithChildren<{ + releases: Array; +}>; + +const WithDownloadSection: FC = ({ + releases, + children, +}) => { const locale = useLocale(); - const releases = provideReleaseData(); const snippets = provideDownloadSnippets(locale); const { pathname } = getClientContext(); diff --git a/apps/site/components/withNodeRelease.tsx b/apps/site/components/withNodeRelease.tsx index 49b7a38bd0fc0..078c0c8ceed76 100644 --- a/apps/site/components/withNodeRelease.tsx +++ b/apps/site/components/withNodeRelease.tsx @@ -1,3 +1,5 @@ +'use server'; + import type { FC } from 'react'; import provideReleaseData from '#site/next-data/providers/releaseData'; @@ -11,11 +13,11 @@ type 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. -const WithNodeRelease: FC = ({ +const WithNodeRelease: FC = async ({ status, children: Component, }) => { - const releaseData = provideReleaseData(); + const releaseData = await provideReleaseData(); const matchingRelease = releaseData.find(release => [status].flat().includes(release.status) diff --git a/apps/site/components/withReleaseSelect.tsx b/apps/site/components/withReleaseSelect.tsx index e48a94b19a98e..5dbd64c009d8c 100644 --- a/apps/site/components/withReleaseSelect.tsx +++ b/apps/site/components/withReleaseSelect.tsx @@ -1,10 +1,9 @@ -'use client'; +'use server'; -import WithNoScriptSelect from '@node-core/ui-components/Common/Select/NoScriptSelect'; +import StatelessSelect from '@node-core/ui-components/Common/Select/StatelessSelect'; import type { ComponentProps, FC } from 'react'; import Link from '#site/components/Link'; -import { useRouter } from '#site/navigation.mjs'; import provideReleaseData from '#site/next-data/providers/releaseData'; import type { NodeRelease } from '#site/types'; import { STATUS_ORDER } from '#site/util/download'; @@ -39,21 +38,19 @@ const groupReleasesByStatus = (releases: Array) => { }; type WithReleaseSelectProps = Omit< - ComponentProps, - 'values' | 'as' | 'onChange' + ComponentProps, + 'values' | 'as' >; -const WithReleaseSelect: FC = ({ ...props }) => { - const releaseData = provideReleaseData(); - const { push } = useRouter(); +const WithReleaseSelect: FC = async ({ ...props }) => { + const releaseData = await provideReleaseData(); const navigation = groupReleasesByStatus(releaseData); return ( - diff --git a/apps/site/layouts/Download.tsx b/apps/site/layouts/Download.tsx index 79f589bbf04f7..df5fc573474a9 100644 --- a/apps/site/layouts/Download.tsx +++ b/apps/site/layouts/Download.tsx @@ -4,12 +4,15 @@ import { getClientContext } from '#site/client-context'; import WithDownloadSection from '#site/components/withDownloadSection'; import WithFooter from '#site/components/withFooter'; import WithNavBar from '#site/components/withNavBar'; +import provideReleaseData from '#site/next-data/providers/releaseData'; import styles from './layouts.module.css'; -const DownloadLayout: FC = ({ children }) => { +const DownloadLayout: FC = async ({ children }) => { const { frontmatter } = getClientContext(); + const releases = await provideReleaseData(); + return ( <> @@ -18,7 +21,9 @@ const DownloadLayout: FC = ({ children }) => {

{frontmatter.title}

- {children} + + {children} +
diff --git a/apps/site/next-data/providers/releaseData.ts b/apps/site/next-data/providers/releaseData.ts index 04f37ac05eeda..1ccae9de036dd 100644 --- a/apps/site/next-data/providers/releaseData.ts +++ b/apps/site/next-data/providers/releaseData.ts @@ -1,9 +1,7 @@ -import { cache } from 'react'; +'use cache'; import generateReleaseData from '#site/next-data/generators/releaseData.mjs'; -const releaseData = await generateReleaseData(); - -const provideReleaseData = cache(() => releaseData); +const provideReleaseData = async () => generateReleaseData(); export default provideReleaseData; diff --git a/apps/site/next.config.mjs b/apps/site/next.config.mjs index 8eb650197844e..7396448b79b18 100644 --- a/apps/site/next.config.mjs +++ b/apps/site/next.config.mjs @@ -80,6 +80,7 @@ const nextConfig = { // we also configure ESLint to run its lint checking on all files eslint: { ignoreDuringBuilds: true }, experimental: { + useCache: true, // Ensure that server-side code is also minified serverMinification: true, // Use Workers and Threads for webpack compilation