BikeControl (SwiftControl)
Use Zwift Click, Zwift Play, Shimano Di2, or any Bluetooth controller with MyWhoosh, Rouvy, Zwift, and more. Free to try on iOS, Android, Windows, and macOS.
Your teams' news and games, with scores hidden until you reveal them.
Sports & fitnessJavaScript · TypeScriptGitHub @PeterTechDevOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Personal NBA dashboard — live scores, standings, schedule, and Reddit feed. No ads, no algorithm, no FOMO.
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Live → my-nba-feed.vercel.app
Development, configuration and checks are documented below. Operational details: API cache and quota · YouTube sync.
Modern sports apps aren't built for fans — they're built for engagement metrics.
You open the NBA app to check one score and get hit with ads, push notification prompts, recommended content you didn't ask for, and an algorithm trying to keep you scrolling. Reddit buries the game thread under memes and hot takes. ESPN wants you to subscribe. Google shows you highlights you didn't want to see yet.
The result: checking on your team becomes a chore. You either get spoiled, get distracted, or give up and wait for someone to tell you.
NBA Feed cuts all of that out.
One page. Your team. The score, the record, the next game and the schedule — with spoiler-free mode on by default so you control when you see results. No ads. No algorithm. No recommendations. No paywall. No FOMO loop.
Just basketball, on your terms.
External articles, video thumbnails, playback and YouTube can reveal results. Full-game thumbnails remain visible with protection enabled; Top Plays previews require a reveal while protection is enabled. The app has no advertising of its own, but embedded YouTube videos may show ads and recommendations.
Option C — The Feed is the approved logo direction, selected on October 27, 2026:
an amber basketball/feed mark with a condensed My NBA Feed wordmark. The primary
logo and horizontal, light-background and standalone-mark variations are saved in
public/branding/. See the brand guide for
asset links, intended uses, generation prompts and current limitations.
The shared navigation displays the primary stacked logo, and the standalone mark is encoded as the browser favicon. Application copy continues to use NBA Feed as its short name.
| Layer | Tech |
|---|---|
| Framework | Next.js 16.4.3 (App Router, installed version) |
| Language | TypeScript |
| UI | React 18, Tailwind CSS 3, Lucide React |
| State | Zustand for persisted team choices; React context for games and spoiler settings |
| Data | BALLDONTLIE, Google News RSS, ESPN roster fallback, official NBA YouTube uploads |
| Storage | Upstash Redis Free documents and Next.js Data Cache |
| Deployment | Vercel |
src/app/ # Pages, metadata and API route handlers
src/components/ # Arena header, games, news, media and shared controls
src/hooks/ # Team preferences, spoiler state and shared fetching
src/lib/ # Data contracts, sources, dates, quota and Redis helpers
scripts/ # YouTube publisher
tests/ # Node tests, local Playwright specs and regressions
tests/preview/ # Playwright checks for deployed previews
.github/workflows/ # CI, preview checks and hourly data syncs
public/ # Branding, arena image and local NBA team logos
docs/ # Operations and asset guides
| Page | Purpose |
|---|---|
/ | Focus-team dashboard, games, highlights and opt-in news |
/schedule | Team calendar and agenda |
/highlights | Full-game videos for the selected team |
/top-plays | Nightly NBA Top 5/10 countdowns |
/players | Selected team's active roster |
/about | Product purpose and creator |
Use Node.js 22.21.1, the version used in CI, and npm. package.json requires
Node.js >=22.9.0.
# Clone
git clone https://github.com/PeterTechDev/my-nba-feed.git
cd my-nba-feed
# Install
npm ci
# Copy the environment template (do not overwrite an existing .env.local)
Copy-Item .env.example .env.local
# Fill in .env.local, then start the app
# Run locally
npm run dev
Open http://localhost:3000.
On macOS/Linux, use cp .env.example .env.local instead of Copy-Item.
Keep .env.local at the repository root. It is ignored by Git. Restart the dev
server after changing environment variables.
| Variable | Where and why |
|---|---|
BALLDONTLIE_KEY | App server: scheduled catalog sync and league-wide live refresh |
KV_REST_API_URL / KV_REST_API_TOKEN | App server: Upstash Redis snapshots, highlights and shared API budget. Use the connected database credentials for local development. |
DIGEST_PUBLISH_TOKEN | App server and GitHub Actions: matching secret used as a Bearer token by the highlights and game publishers |
YOUTUBE_API_KEY | YouTube sync runner only: GitHub Actions or local highlights:sync. Not required by the app server. |
NEXT_PUBLIC_SITE_URL | Optional canonical HTTPS origin for metadata, sitemap and robots; defaults to VERCEL_PROJECT_PRODUCTION_URL, then the production site |
API and publisher keys are server secrets; never prefix them with NEXT_PUBLIC_.
The environment template lists the core variables; add the optional values you
need. Without a BALLDONTLIE key or working Redis access, game requests cannot
refresh. News does not require an API key; rosters use ESPN directly.
| Command | Purpose |
|---|---|
npm run dev | Start the development server |
npm run build | Create the production build |
npm start | Serve an existing production build |
npm run lint | Run ESLint |
npm run typecheck | Check TypeScript without emitting files |
npm test | Run the Node *.test.mjs suite |
npm run test:ssr | Check the Morning Briefing server-rendered placeholder; requires a current build unless BASE_URL is supplied |
npm run test:e2e | Run Playwright; configure the target as described below |
npm run highlights:sync | Fetch official videos and publish to production; loads .env.local |
| Method and route | Input / behavior |
|---|---|
GET /api/games | teamId; last, current and next game plus record; spoiler-aware |
GET /api/games/live | Today's league games; raw scores |
GET /api/schedule | teamId, optional start and end (YYYY-MM-DD); raw game results, at most a 366-day range |
GET /api/news | team or comma-separated teams; spoiler-aware RSS coverage |
GET /api/players | teamId; active roster with source attribution |
GET /api/highlights | teamId and up to 15 comma-separated recent dates, or kind=top-plays; saved video metadata |
GET /api/highlights?sync=1 | Bearer token required; read synchronization state |
POST /api/highlights | Bearer token required; validate and merge video metadata |
Team IDs come from src/lib/teams.ts. API spoiler redaction applies to
/api/games and /api/news; the live and schedule endpoints expose
results. Spoiler protection is a viewing preference, not an authorization boundary.
GET /api/news and GET /api/games are closed unless the caller asks for the open payload. Every response, open or redacted, sends Vary: x-nba-spoilers and Cache-Control: private, no-store.
| Signal | Result |
|---|---|
No x-nba-spoilers header, or an unknown token | Scores and spoiler headlines redacted |
x-nba-spoilers: none as the only token (None and surrounding spaces count). Empty tokens are ignored, so none; is none | Full payload |
none together with any other token, such as none,scores | Stays closed. none does not open anything unless it is alone |
scores and/or headlines (comma or semicolon, any case, trimmed), with no none | Those parts stay redacted; the other part is included |
hideScores=1 or hideHeadlines=true (name and value are case-insensitive; 1 or true) | Forces that part to stay redacted |
reveal=<id> opens only that item. Every other headline, including others in the same related-coverage group, stays redacted the same way as a request without reveal. The id is a news link or identity, a game id, or record. ?team= and ?teams= use that same grouping and redaction. The app sends the header on every one of these calls, including none when spoiler protection is off.
Connect an Upstash Redis Free store for game snapshots, highlights and API quota storage.
The existing DIGEST_PUBLISH_TOKEN name is retained for the highlights and game publishers.
Digest and standings pages, APIs and publication jobs have been removed; stored
historical documents are untouched.
The YouTube Highlights workflow runs hourly at minute 15 (UTC schedule),
from main, and can also be triggered manually. Page visits never call the
YouTube Data API. See the cache guide and
YouTube guide for first-run setup and recovery.
The main CI workflow runs lint, typecheck, Node tests and build:
npm run lint
npm run typecheck
npm test
npm run build
npm run test:ssr
Playwright needs Chromium (npx playwright install chromium). For local checks,
start the app in another terminal, then run:
$env:BASE_URL = 'http://localhost:3000'
npm run test:e2e
playwright.config.mjs does not start the app. The bypass specs create their own
fixture servers; the reveal-confirmation and preview specs need BASE_URL.
By default, Playwright includes both top-level specs and tests/preview/.
For a deployed preview, set PLAYWRIGHT_PREVIEW=1 to run only tests/preview/
against BASE_URL, and set VERCEL_BYPASS to the deployment protection bypass
secret. These preview helpers require it for non-local origins. The
bypass header is sent only to that origin. Same-origin redirects are followed
with the header for up to 5 hops. POST on 301, 302, or 303 becomes GET and
drops the body. A 307 or 308 keeps the method and body. A redirect to any other origin is returned
as-is, without the header.
The Preview bypass tests workflow runs the two bypass specs locally. The
e2e workflow runs after successful Vercel Preview deployments and uses the
repository secret VERCEL_AUTOMATION_BYPASS_SECRET as VERCEL_BYPASS.
SMOKE_ALLOW_MISSING_API_KEY defaults to empty,
which does not tolerate any 503 console line. Set the repository variable to
1 only when the preview has no BALLDONTLIE key or shared storage. That value
tolerates 503 only for the exact path /api/games.
The app includes a web manifest and install icons. Offline caching and push notifications are not implemented.
Peter Souza — [email protected]
SDET · AI Developer · Building in public
Public game and schedule routes filter one saved league catalog. Visits, team
changes and score reveals do not fetch separate team seasons from BallDontLie.
NBA Games Sync runs at 06:35, 14:35 and 22:35 America/Bahia. GitHub may delay runs.
It retrieves all pages 16 seconds apart and publishes only a complete, validated
catalog. Failures preserve the previous version. It uses DIGEST_PUBLISH_TOKEN.
During a scheduled game's 12-hour window, requests refresh relevant league dates once per minute through a shared Redis lease. Final results remain saved until the next catalog update. No visitors means no live polling. The five-call rolling budget still protects sync and live updates. APIs expose catalog syncedAt. Redis holds snapshots; Next caches the full catalog for one hour, invalidated after a successful publication. Live reads use 60 seconds; shared highlights reads use five minutes. Highlights use a five-minute public CDN cache. Spoiler responses remain private. ESPN rosters use a daily cache, news an hourly cache. Browsers poll once per minute and honor Retry-After. External calls log balldontlie_request and status without credentials.
Before rollout, populate the production Redis catalog through the authenticated POST /api/sync/games endpoint. Then enable .github/workflows/sync-games.yml on main. Keep Upstash Free and auto-upgrade disabled. Free infrastructure quotas still apply.