Skip to content

SSR the download panel - #17

Merged
BeckettFrey merged 4 commits into
mainfrom
ssr-download-panel
Aug 19, 2026
Merged

SSR the download panel#17
BeckettFrey merged 4 commits into
mainfrom
ssr-download-panel

Conversation

@BeckettFrey

Copy link
Copy Markdown
Member

Closes #3.

DownloadButton fetched /api/releases from the browser on every visit — a GitHub round trip per visitor, a loading flash before anything useful appeared, and no version numbers for crawlers.

It now renders on the server. lib/releases.ts holds the fetch and grouping logic lifted from the route handler; DownloadButton is an async server component that awaits it and passes the result to DownloadButtonClient for the interactive parts. /download drops "use client" so it prerenders (○ Static, Revalidate: 1d) with real data and metadata in the HTML. app/api/releases/route.ts is deleted — no callers left.

A cron in vercel.json hits /api/revalidate-releases at 00:00 UTC to purge the cache tag and the prerendered page, so the first visitor after midnight triggers one fetch. Roughly one GitHub request per day instead of one per visitor. A new release therefore takes up to a day to appear — the grace period #3 asks for.

Tiles now read macOS, Windows, Linux.

Reviewer notes

Hydration. SSR introduced two ways server and client could disagree: OS detection (no user agent server-side) now uses useSyncExternalStore with selectedOS derived rather than stored, so both renders are identical by construction; dates go through a fixed en-US/UTC Intl.DateTimeFormat, since toLocaleDateString() would resolve against the viewer's timezone.

OS_ORDER was doing two jobs — tile layout and the fallback OS. That fallback is what gets prerendered and indexed, so reordering in place would have quietly moved the indexed default from Windows to macOS. Split into OS_DISPLAY_ORDER and OS_FALLBACK_ORDER; only the former changed.

Before merging

Set CRON_SECRET in the Vercel dashboard, Production-scoped. Vercel sends it as a bearer token on cron invocations; the endpoint 500s when it's missing rather than running unauthenticated.

Verification

lint, typecheck, format:check, next build pass. Against live GitHub: prerendered HTML contains v0.5.0, VoxKit-setup.exe and the pre-release banner with no loading state; per-OS resolution correct (Windows v0.5.0, macOS v0.4.1, Linux greyed out); revalidate endpoint 401s unauthenticated and on a wrong secret, 200s with the right one, after which the page re-rendered with an advanced timestamp.

Hydration is safe by construction but was not confirmed in a browser console — worth a look at the preview deploy.

Not in scope

The DownloadButtonDownloadPanel rename is #2; doing it here would collide with that PR.

DownloadButton fetched /api/releases from the browser on every visit, so
each visitor cost a GitHub round trip, the panel flashed a loading state,
and crawlers saw no version numbers at all.

Resolve the data during render instead. lib/releases.ts holds the fetch
and grouping logic lifted from the route handler; DownloadButton becomes
an async server component that awaits it and hands the result to
DownloadButtonClient for the interactive parts. /download drops
"use client" so it can prerender, and gains its own metadata.

Two details the client-side version did not have to worry about:

- OS detection has no user agent to read on the server, so it moves to
  useSyncExternalStore and selectedOS becomes derived rather than stored.
  The server render and the hydration render are then identical by
  construction. (An effect would also trip react-hooks/set-state-in-effect.)
- Dates are formatted through a fixed en-US/UTC Intl.DateTimeFormat.
  toLocaleDateString() resolves against the viewer's timezone, which would
  mismatch the prerendered HTML on hydration.

getReleases() never throws: a GitHub outage must not fail the build, and
on an already prerendered page a failed revalidation leaves the last good
render in place.

Deletes app/api/releases/route.ts, which existed only to serve this
component and now has no callers.
The server render needs something to invalidate it, and issue #3 asks for
a once-daily refresh at midnight so a new release gets a grace period
before the website advertises it.

vercel.json crons /api/revalidate-releases at 00:00 UTC. The handler
purges the "releases" cache tag and the prerendered /download page, so
the first visitor after midnight triggers exactly one upstream fetch.
That puts the site at roughly one GitHub request per day rather than one
per visitor, well inside the 60/hour unauthenticated limit.

The endpoint requires CRON_SECRET, which Vercel sends as a bearer token
on cron invocations. It 500s when the variable is missing rather than
running unauthenticated, so a misconfigured deploy fails loudly.
The up-to-a-day delay before a new release appears on the site is
deliberate, but nothing in the repo said so, and a maintainer hitting it
would reasonably read it as a bug. Records the grace period, the manual
override for publishing immediately, and the fact that CRON_SECRET must
exist in the Vercel dashboard before this deploys.

Also notes that the page's revalidate literal duplicates
RELEASES_REVALIDATE_SECONDS because Next cannot accept an import there,
so the two have to move together.
Tiles now read macOS, Windows, Linux.

OS_ORDER was doing two jobs: laying out the tiles and picking the OS the
panel falls back to when detection fails. Since the server has no user
agent to sniff, that fallback is what the prerendered HTML shows and what
crawlers index, so reordering the constant in place would have quietly
moved the indexed default from Windows to macOS.

Splits it into OS_DISPLAY_ORDER and OS_FALLBACK_ORDER. Only the former
changes here; the prerendered default is still Windows.
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
voxkit-web Ready Ready Preview Aug 19, 2026 9:59pm

@BeckettFrey BeckettFrey changed the title SSR the download panel (closes #3) SSR the download panel Aug 19, 2026
@BeckettFrey
BeckettFrey merged commit 513cb7e into main Aug 19, 2026
5 checks passed
@BeckettFrey
BeckettFrey deleted the ssr-download-panel branch August 19, 2026 22:05
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.

Enable SSR For DownloadButton

1 participant