Skip to content

feat: Introduce Downloads Archive page #7794

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

Open
wants to merge 57 commits into
base: main
Choose a base branch
from

Conversation

canerakdas
Copy link
Member

@canerakdas canerakdas commented May 31, 2025

Description

This PR introduces a new statically generated page that displays simplified download options by major version:

image

Also includes a download table of other minor versions of the major version;
image

Since I included the minor versions as a table, the content became quite lengthy. To prevent this, I used the HTML details element. The main reason I didn't use Radix Primitive Accordion is that we want this page to be fully usable even for users who have JavaScript disabled

I am open to suggestions for to add / remove content 🙇

Validation

The links below are accessible in the preview;

  • /download/{major}
  • /download/archive

Related Issues

Addresses #7443

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run pnpm format to ensure the code follows the style guide.
  • I have run pnpm test to check if all tests are passing.
  • I have run pnpm build to check if the website builds without errors.
  • I've covered new added functionality with unit tests if necessary.

@Copilot Copilot AI review requested due to automatic review settings May 31, 2025 12:02
@canerakdas canerakdas requested a review from a team as a code owner May 31, 2025 12:02
Copy link

vercel bot commented May 31, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
nodejs-org ✅ Ready (Inspect) Visit Preview Aug 10, 2025 11:10pm

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new simplified downloads page that’s fully server-rendered (no JS required) and provides major‐version download summaries, with expandable minor versions under <details>. It also centralizes download utilities and updates the download URL API to use an options object.

  • Introduce simplified.mdx and DownloadSimpleLayout for the new page
  • Update getNodeDownloadUrl signature and its callers to use an options object
  • Add DownloadsTable, Details, and WithSimplifiedDownload components

Reviewed Changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
util/downloadUtils/index.tsx Exported DownloadDropdownItem type
util/downloadUtils/constants.json Added compatibility ranges for platforms
util/tests/getNodeDownloadUrl.test.mjs Updated tests for new getNodeDownloadUrl signature
types/layouts.ts Added 'download-simple' layout type
pages/en/download/simplified.mdx New MDX page with simplified download UI
next.mdx.use.mjs Registered new MDX components (WithSimplifiedDownload, etc.)
next.dynamic.constants.mjs Configured dynamic routing and exclusions for new page
layouts/DownloadSimple.tsx New layout wrapping simplified download content
components/withSimplifiedDownload.tsx HOC to provide download data and sidebar items
components/withProgressionSidebar.tsx Refactored to accept both navKey and explicit groups
components/withMetaBar.tsx Made items injectable for meta‐bar
components/withMarkdownContent.tsx New HOC to load MDX content at runtime
components/withLayout.tsx Added DownloadSimpleLayout to layout mapping
components/MDX/Details New <Details> component for collapsible sections
components/Downloads/Release/ReleaseCodeBox.tsx Updated “no-script” link to point at new simplified page
components/Downloads/Release/PrebuiltDownloadButtons.tsx Switched getNodeDownloadUrl calls to object syntax
components/Downloads/DownloadsTable New table for listing artifacts
components/Downloads/DownloadLink.tsx Updated to new getNodeDownloadUrl API
components/Downloads/DownloadButton Updated to new getNodeDownloadUrl API
Comments suppressed due to low confidence (1)

apps/site/components/Downloads/DownloadsTable/index.tsx:1

  • Consider adding unit tests for DownloadsTable to verify it renders rows correctly for different source inputs.
'use client';

Co-authored-by: Copilot <[email protected]>
Signed-off-by: Caner Akdas <[email protected]>
@avivkeller
Copy link
Member

@canerakdas What's the status here?

@canerakdas
Copy link
Member Author

What's the status here?

I’m waiting for more feedback and reviews / approval 😄

@@ -22,11 +22,21 @@ const PrebuiltDownloadButtons: FC = () => {
const { release, os, platform } = useContext(ReleaseContext);

const installerUrl = platform
? getNodeDownloadUrl(release.versionWithPrefix, os, platform, 'installer')
? getNodeDownloadUrl({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC, why is this function now taking an object?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some parameters in this function are optional for example, when generating a shasum or source link, providing only the version and kind is sufficient. Instead of calling

getNodeDownloadUrl(version, undefined, undefined, 'source')

it feels more readable to use a named parameter approach;

getNodeDownloadUrl({ version, kind: 'source' })


import { dynamicRouter } from '#site/next.dynamic';

const getMarkdownContent = async (locale: string, file: Array<string>) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC, why we need this?


<div className={styles.downloadLayout}>
<main>
<WithMarkdownContent file={['download', 'archive']} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this over here and not coming from the dynamic router?

@@ -29,6 +32,14 @@ export const IGNORED_ROUTES = [
* @type {Map<string, import('./types').Layouts>} A Map of pathname and Layout Name
*/
export const DYNAMIC_ROUTES = new Map([
// Creates dynamic routes for downloads archive pages for each version
// (e.g., /download/v18.20.8, /download/v20.19.2)
...provideReleaseData()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My fear is that this will blow static builds in a monstruous way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also calling the provider right here in a constant file that could be called in a non-react environment might not be a good idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a number of how many more routes this generates?

Copy link
Member Author

@canerakdas canerakdas Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, there are 606 pages for the default locale alone. Should all of them be included in the static build, or should we selectively build only the ones that are most frequently accessed (lts, current, or last X version)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do the following on the ignore list:

  • locale isn't defaultLocale and paths starts with /download/archive

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how long the static build takes in this branch?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Speed seems actually quite ok. Btw seeing this error on the build "Error: Cannot apply utility class after:md:-left-1/2 because the md variant does not exist."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Speed seems actually quite ok. Btw seeing this error on the build "Error: Cannot apply utility class after:md:-left-1/2 because the md variant does not exist."

Rebase, it's been fixed

file: Array<string>;
};

const WithMarkdownContent: FC<WithMarkdownContentProps> = async ({ file }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can only imagine that your markdown content is not being rendered, but probably because the next.dynamic.router was designed for the generated routes to not include markdown, maybe that can be tweaked. I don't think we should have this WithMarkdownContent component, and have yet another place that can read files and markdown. That could further complicate things for Open Next (Cloudflare)

status: NodeReleaseStatus;
};

const WithReleaseAlertBox: FC<WithReleaseAlertBoxProps> = ({ status }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

return (
<AlertBox
title={t('components.common.alertBox.info')}
level="info"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we creeate a green alert box variant and use this one here?

@@ -70,8 +70,12 @@ const generateReleaseData = async () => {
const status = getNodeReleaseStatus(new Date(), support);

const minorVersions = Object.entries(major.releases).map(([, release]) => ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know the more data you add, the bigger the whole thing becomes. I'm only afraid on bundle size changes and how bigger of a footprint this change especifically will do on our client-bundles and HTMLs

@@ -19,6 +20,8 @@ export const IGNORED_ROUTES = [
locale !== defaultLocale.code && /^blog/.test(pathname),
// This is used to ignore all pathnames that are empty
({ locale, pathname }) => locale.length && !pathname.length,
// This is used to ignore download routes for Node.js versions and downloads archive page
({ pathname }) => /^download\/(v\d+(\.\d+)*|archive)$/.test(pathname),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this ignore? btw instead of the URLs being /download/vXX/archive it should be /download/archive/{xVV} this would probably remove the need to also have this ignore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe I am misreading this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this /download/v{XX} and /download/archive?

Copy link
Member

@ovflowd ovflowd Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the /download/archive url should probably not exist. As it could be ambiguous with /current and /lts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd only make sense if we had a /download/archive and /download/archive/v{XX}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this /download/v{XX} and /download/archive?

This is intended for /download/v{XX} and /download/archive, where /download/archive represents the LTS release. I think it could be useful in places where we want to provide a fixed link

It'd only make sense if we had a /download/archive and /download/archive/v{XX}

I also think the page URLs should follow this format. I’ll look into removing them from the ignore list (I might have misunderstood part of the issue description)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't make sense for /download/archive be the LTS page when /download is the LTS page.

IMO the /download/archive/{version} makes more sense for me, and /download/archive could redirect to /download/archive/{latestVersion} (not LTS)

The reason we want /download go to LTS is just that for newcomers that is the version we want to direct people towards. But the archive entry point should always be the latest version. And the redirect should be so that users get redirected for what was at the point they accessed the latest version

// Creates dynamic routes for downloads archive pages for each version
// (e.g., /download/v18.20.8, /download/v20.19.2)
...provideReleaseData()
.flatMap(({ minorVersions, versionWithPrefix }) => [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a flatMap make two separate maps for readability, as we did for the blog categoreis and posts... Also instead of relying on provideReleaseData() maybe the providers could have a simple provideReleaseVersions() including all our versions.

Copy link
Contributor

github-actions bot commented Aug 10, 2025

Lighthouse Results

URL Performance Accessibility Best Practices SEO Report
/en 🟢 97 🟢 100 🟢 100 🟢 100 🔗
/en/about 🟢 99 🟢 97 🟢 100 🟠 88 🔗
/en/about/previous-releases 🟢 99 🟢 93 🟢 100 🟠 89 🔗
/en/download 🟢 96 🟢 100 🟢 100 🟢 100 🔗
/en/blog 🟢 99 🟢 100 🟢 96 🟢 100 🔗


<h2 className="flex items-center gap-2">
<img
src="/static/images/favicons/favicon.png"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have an SVG ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Simplified Downloads page for non-JS-enabled environments
6 participants