Skip to content

feat(website): direct APK download via /download route handler#269

Merged
RaheemJnr merged 1 commit into
mainfrom
feat/website-direct-apk-download
May 21, 2026
Merged

feat(website): direct APK download via /download route handler#269
RaheemJnr merged 1 commit into
mainfrom
feat/website-direct-apk-download

Conversation

@RaheemJnr
Copy link
Copy Markdown
Owner

@RaheemJnr RaheemJnr commented May 21, 2026

Summary

Replace the four Download APK buttons (Hero, Closing CTA, desktop navbar, mobile drawer) that navigated to the GitHub releases page with a single Route Handler at /download that resolves the latest release's .apk asset at request time and 302-redirects to it.

Click flow: user taps Download APK → browser hits /download → server-side handler queries the GitHub API → 302 redirect to https://github.com/.../releases/download/v<X.Y.Z>/PocketNode-v<X.Y.Z>.apk → browser starts the file download.

Why not a static link

GitHub's /releases/latest/download/<filename> redirect requires the asset name to be stable across releases. Our APKs are named PocketNode-v<version>.apk, so a static link would go stale on every release. The handler always points at the current cut.

Caching

  • Vercel edge cache: Cache-Control: public, s-maxage=600, stale-while-revalidate=3600
  • Upstream fetch to api.github.com: next.revalidate: 600

GitHub's anonymous API quota is 60 requests/hour. With a 10-minute cache window the website would need 60 distinct edge nodes to hit the limit, which is well above typical traffic.

Failure mode

If the GitHub API call fails for any reason (rate limit, network blip, malformed response) the handler falls back to redirecting to the GitHub release page. No 500s; users can still grab the APK manually.

Test plan

  • npm run build shows /download as ƒ (dynamic), other 6 routes still static
  • On the Vercel preview, tapping Download APK in the hero, closing CTA, navbar, and mobile drawer all kick off a download of the current PocketNode-v<latest>.apk
  • If GitHub is briefly unreachable, the user lands on releases/latest instead of an error page

Summary by CodeRabbit

  • New Features

    • Added an internal download route for Android APK files that automatically fetches and redirects to the latest release.
  • Improvements

    • Updated download buttons across the website (Hero, Navbar, and main page) to use the new internal path, providing faster access with improved caching.

Review Change Stack

Replaces the four Download APK buttons (Hero, Closing CTA, desktop
navbar, mobile drawer) that previously navigated to the GitHub
releases page with a single Route Handler at /download that resolves
the latest release's .apk asset at request time and 302-redirects
to it. The user clicks Download APK and the browser kicks off a
normal file download without leaving the site visually.

Why not a static link

GitHub's `/releases/latest/download/<filename>` redirect requires
the asset name to be stable across releases. Our APKs are named
`PocketNode-v<version>.apk`, so the name shifts every cut — a
static link would go stale on every release. The handler asks the
GitHub API for the latest release, picks the .apk asset, and
redirects to its `browser_download_url` which always points at
the current cut.

Caching

The redirect itself is edge-cached on Vercel for 10 minutes with
stale-while-revalidate of 1 hour, so the GitHub API quota
(60 requests/hour anonymous) is not a bottleneck even during a
launch spike. New releases propagate inside ~10 minutes worst case.

Failure mode

If the GitHub API fails (rate limit, network blip, malformed
response) the handler falls back to redirecting to the GitHub
release page so the user can still grab the APK by hand. No 500s.

Build verified: /download shows as `ƒ` (Dynamic, server-rendered)
in the route table; the other 6 routes still prerender static.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

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

Project Deployment Actions Updated (UTC)
pocket-node Ready Ready Preview, Comment May 21, 2026 7:48pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

A new internal /download route handler resolves the latest GitHub APK release and redirects to its download URL. All website UI components (Hero, Navbar, page) are updated to link to /download instead of directly to external GitHub release URLs, removing external-link attributes.

Changes

Centralized APK Download Routing

Layer / File(s) Summary
Download route handler implementation
website/app/download/route.ts
New Next.js GET handler at /download fetches the latest GitHub release via API, extracts the first .apk asset, and returns a 302 redirect to the asset's browser_download_url with edge cache control (s-maxage=600, stale-while-revalidate=3600). Includes TypeScript interfaces to model the GitHub release response and configuration constants for the GitHub API endpoint and fallback URL. On any fetch failure, JSON parsing error, or missing .apk asset, logs the error and redirects to the GitHub releases page instead.
UI component link updates
website/app/page.tsx, website/components/Hero.tsx, website/components/Navbar.tsx
Updated download CTAs across Hero, Navbar (desktop and mobile), and ClosingCta sections to point to the internal /download route instead of external GitHub release URLs. Removed target="_blank" and rel="noopener noreferrer" attributes from affected links. Hero component adds inline comments describing the /download route's runtime redirection behavior.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A rabbit hops down a path more direct,
GitHub's redirect now perfectly wrecked—
No more leaving the garden for links so deep,
One route to fetch 'em, one promise to keep!
Download with grace through the /download gate.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing a new /download route handler for APK downloads, which is central to all modifications across four UI components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/website-direct-apk-download

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@website/app/download/route.ts`:
- Line 65: The current asset selection uses a loose find: apkAsset =
release.assets?.find(asset => asset.name.endsWith('.apk')), which can pick the
wrong APK when multiple APKs exist; update the matching to target the exact
filename contract PocketNode-v<version>.apk instead. Extract or derive the
release version (e.g., from release.tag_name or the version variable) and
replace the endsWith check with a strict match (either equality to
`PocketNode-v${version}.apk` or a regex anchored to
`^PocketNode-v${escapedVersion}\.apk$`) when calling release.assets?.find so
only the intended artifact is selected before redirecting.
- Line 86: The fallback redirect currently returns
NextResponse.redirect(FALLBACK_URL, { status: 302 }) with no caching, causing
repeated upstream calls; update the fallback branch that calls
NextResponse.redirect to attach a short Cache-Control header (for example
Cache-Control: public, max-age=60 or similar) so the 302 fallback response is
cached briefly when GitHub is degraded; locate the redirect call that references
FALLBACK_URL and NextResponse.redirect and add headers to the response before
returning it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: be24868a-3d41-4a7f-abfc-ef93c5516c98

📥 Commits

Reviewing files that changed from the base of the PR and between aac6e30 and bb05fae.

📒 Files selected for processing (4)
  • website/app/download/route.ts
  • website/app/page.tsx
  • website/components/Hero.tsx
  • website/components/Navbar.tsx

}

const release = (await response.json()) as LatestRelease
const apkAsset = release.assets?.find((asset) => asset.name.endsWith('.apk'))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Tighten APK asset matching to avoid redirecting to the wrong artifact.

Line 65 currently picks the first *.apk, which is ambiguous if a release includes multiple APKs (e.g., debug/universal/split builds). Match the intended filename contract (PocketNode-v<version>.apk) explicitly before redirecting.

Proposed fix
-    const apkAsset = release.assets?.find((asset) => asset.name.endsWith('.apk'))
+    const apkAsset = release.assets?.find((asset) =>
+      /^PocketNode-v\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?\.apk$/.test(asset.name)
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const apkAsset = release.assets?.find((asset) => asset.name.endsWith('.apk'))
const apkAsset = release.assets?.find((asset) =>
/^PocketNode-v\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?\.apk$/.test(asset.name)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/app/download/route.ts` at line 65, The current asset selection uses a
loose find: apkAsset = release.assets?.find(asset =>
asset.name.endsWith('.apk')), which can pick the wrong APK when multiple APKs
exist; update the matching to target the exact filename contract
PocketNode-v<version>.apk instead. Extract or derive the release version (e.g.,
from release.tag_name or the version variable) and replace the endsWith check
with a strict match (either equality to `PocketNode-v${version}.apk` or a regex
anchored to `^PocketNode-v${escapedVersion}\.apk$`) when calling
release.assets?.find so only the intended artifact is selected before
redirecting.

// we send the user to the GitHub release page rather than show
// them a 500. They can grab the .apk by hand from there.
console.error('Failed to resolve latest APK asset:', error)
return NextResponse.redirect(FALLBACK_URL, { status: 302 })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cache fallback redirects as well to reduce repeated failing upstream calls.

When GitHub is degraded, every /download request re-hits the API before falling back. Adding a short Cache-Control on the fallback redirect improves resilience and latency during incidents.

Proposed fix
-    return NextResponse.redirect(FALLBACK_URL, { status: 302 })
+    return NextResponse.redirect(FALLBACK_URL, {
+      status: 302,
+      headers: {
+        'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600',
+      },
+    })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return NextResponse.redirect(FALLBACK_URL, { status: 302 })
return NextResponse.redirect(FALLBACK_URL, {
status: 302,
headers: {
'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600',
},
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/app/download/route.ts` at line 86, The fallback redirect currently
returns NextResponse.redirect(FALLBACK_URL, { status: 302 }) with no caching,
causing repeated upstream calls; update the fallback branch that calls
NextResponse.redirect to attach a short Cache-Control header (for example
Cache-Control: public, max-age=60 or similar) so the 302 fallback response is
cached briefly when GitHub is degraded; locate the redirect call that references
FALLBACK_URL and NextResponse.redirect and add headers to the response before
returning it.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb05faec14

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

assets?: ReleaseAsset[]
}

export const dynamic = 'force-dynamic'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove force-dynamic so upstream fetch can be cached

The dynamic = 'force-dynamic' segment config forces this route’s fetch() calls to run as no-store, so the next: { revalidate: 600 } option below is effectively negated. That means every uncached /download invocation can hit api.github.com, which risks tripping GitHub’s anonymous rate limit and pushing users into the fallback redirect instead of a direct APK download. Please use a cache-compatible segment setting (or drop force-dynamic) so the 10-minute upstream cache policy actually applies.

Useful? React with 👍 / 👎.

@RaheemJnr RaheemJnr merged commit 3ffdbe8 into main May 21, 2026
7 checks passed
@RaheemJnr RaheemJnr deleted the feat/website-direct-apk-download branch May 21, 2026 19:56
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.

1 participant