A production-ready Next.js application that tracks Purdue guard Braden Smith’s career assists and his progress toward Bobby Hurley’s NCAA Division I record (1,076). The site auto-ingests public ESPN data, falls back to NCAA sources when needed, and presents Purdue-branded dashboards, standings, and interactive game logs.
- Live Purdue branding: Gold/black color system, display numerals, and diagonal band motifs inspired by Purdue Brand Studio guidance.
- Automated ingestion: ESPN JSON (primary) → ESPN HTML (fallback) → NCAA stats (ultimate fallback) with JSON snapshot storage.
- GitHub Actions automation: Automated stats updates via GitHub Actions workflow that runs on schedule and commits changes automatically.
- Live game updates: Real-time assist tracking during live games with client polling that refreshes only when values change.
- Hero pace insights: Progress bar, required averages, and comparisons to season/career/last-5 metrics.
- Market-aware scenario odds: Postseason scenario likelihoods use ESPN moneyline implied probabilities (no-vig) when posted, then fall back to baseline round assumptions for games without lines.
- Visual analytics: Assist sparkline chart with season boundary markers (Chart.js + react-chartjs-2).
- Comprehensive tables:
- Top-10 all-time assist leaderboard with Braden highlighted and deltas to the next player.
- Full career game log with season boundaries, results, and ESPN boxscore links.
- Recent game recap table emphasizing assist totals.
- Deployment ready: Secured cron ingest endpoint, health check, and documented environment variables for Cloudflare Pages or Cloud Run.
- Next.js 16 (App Router) + TypeScript
- Tailwind CSS (v4) with Purdue-themed design tokens
- Chart.js + react-chartjs-2
- Date-fns for formatting
- Node fetch with lightweight caching utilities
| Task | Command |
|---|---|
| Run locally | npm run dev → http://localhost:3000 |
| Lint | npm run lint |
| Test | npm run test |
| Build | npm run build |
| Refresh March Madness scenario model | npm run update-scenario-model |
| Deploy (Cloudflare) | npm run deploy |
| Preview (Cloudflare) | npm run preview |
- Node.js 18+ (Node 22 LTS recommended)
- npm (comes with Node) or pnpm/yarn/bun if you prefer
npm installCopy .env.example (create it if it does not yet exist) and override as needed:
| Variable | Default | Description |
|---|---|---|
ESPN_BASE |
https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball |
Override if ESPN changes hostname. |
CACHE_TTL_NORMAL |
3600 seconds |
TTL for cached responses during idle periods. |
CACHE_TTL_LIVE |
300 seconds |
TTL during live-game windows. |
CRON_TOKEN |
(required for prod) | Shared secret used to authorize ingest calls. |
NEXT_PUBLIC_SITE_URL |
https://bradensmithassists.com |
Canonical site URL used for metadata/sitemap links. Set to your Cloudflare workers.dev or custom domain for correct self-fetch behavior. |
NEXT_PUBLIC_CF_BEACON_TOKEN |
— | Cloudflare Web Analytics token. Get it from Cloudflare Dashboard → Web Analytics → Add site. Optional; omit to disable analytics. |
The repository includes data/leaders.json and a starter public/data/tracker.json for local development. The first cron run (or manual ingest) will overwrite this file with fresh data.
npm run devVisit http://localhost:3000.
npm run lint # ESLint + TypeScript
npm run test # Vitest unit tests for metrics
npm run build # Production build smoke test- Primary endpoint:
GET /api/trackerreturns the normalized payload used across pages. - Cron endpoint:
POST /api/ingest(requiresAuthorization: Bearer <CRON_TOKEN>or?token=) re-fetches ESPN data, recomputes summaries, and persists the snapshot topublic/data/tracker.json. - Health check:
GET /api/healthz→{ status: "ok" }for uptime monitors.
The project includes a GitHub Actions workflow (.github/workflows/update-stats.yml) that automatically updates stats:
- Scheduled runs: Checks every 30 minutes (uses
scripts/should-update.tsto run updates only when needed) - Update windows: 30 minutes before tip through 8 hours after tip; daily noon EST; when games went final but aren't in
gameLogyet - Manual trigger: Available from the GitHub Actions UI
- Auto-commit: Automatically commits updated
tracker.jsonwhen changes are detected - Cloudflare Pages: Auto-deploys on push when connected via Git integration
The workflow runs npm run update-data which fetches fresh data from ESPN and updates the tracker snapshot. See AUTOMATION_OPTIONS.md for more details.
When Kalshi or Polymarket tournament markets move, refresh the March Madness baseline model with:
npm run update-scenario-modelUse npm run update-scenario-model -- --dry-run to preview the derived percentages without writing /Users/adamoberley/Developer/bradensmithassiststracker/data/scenario-probability-model.json.
Optional flags:
npm run update-scenario-model -- --polymarket-event-slug 2026-ncaa-tournament-winnernpm run update-scenario-model -- --scan-title-marketnpm run update-scenario-model -- --ncaa-first-game-team-ml -8000 --ncaa-first-game-opponent-ml +2200The updater uses Kalshi stage markets plus Polymarket team winner odds, then computes volume-weighted championship true odds. It also reads public/data/tracker.json to mark already-completed NCAA rounds before converting stage odds into per-round win percentages.
During live games, the site can display real-time assist counts:
- Server-side: The
/api/trackerendpoint automatically fetches live stats when a game is marked asisLive - Client-side:
LiveDataUpdaterpolls/api/trackerduring live windows and refreshes the route only when tracked values change - Setup: See
LIVE_UPDATES_QUICK_START.mdfor quick setup instructions
If using Cloudflare Cron Triggers or Cloud Scheduler instead of GitHub Actions:
- Game window: Run every 5 minutes during Purdue games.
- Post-game buffer: Continue running hourly for six hours after the final horn.
- Off days: Run once daily at 00:00 ET to capture late updates.
On Cloudflare Pages, use Cron Triggers or external cron services to hit /api/ingest?token=.... On Cloud Run, use Cloud Scheduler with the same endpoint.
-
Cloudflare Pages (recommended)
- See
CLOUDFLARE_DEPLOYMENT.mdfor full setup instructions. - Connect GitHub repo in Cloudflare dashboard; build command:
npx @opennextjs/cloudflare build; deploy:npm run deploy. - Set environment variables:
NEXT_PUBLIC_SITE_URL(your pages.dev URL or custom domain),CRON_TOKEN(optional, for ingest API).
- See
-
Cloud Run / Other
- Use containerized deployment (
npm run build && npm run start). - Configure environment variables (
CRON_TOKEN,ESPN_BASE, etc.) and cron scheduling strategy. - Note: The app uses file-based storage (
public/data/tracker.json), so ensure your deployment preserves this file or uses GitHub Actions for automated updates.
- Use containerized deployment (
public/fallback.htmlis a cached backup page generated frompublic/data/tracker.json.- Generate it manually with
npm run generate-fallback. - Scheduled GitHub updates now commit both
public/data/tracker.jsonandpublic/fallback.html. - If your Cloudflare plan supports Custom Errors, map 1xxx Worker errors (including 1027) to
https://your-domain.com/fallback.html. - Important: an in-app Next.js route cannot catch 1027 because Cloudflare blocks requests before your Worker executes.
- Primary sans font: Work Sans (stand-in for Acumin Pro).
- Display numerals: Bebas Neue (stand-in for United Sans).
- Serif passages: Source Serif 4 (matches Source Serif Pro guidance).
- If you have licenses for Purdue brand fonts, add them under
public/fontsand updateapp/layout.tsxaccordingly.
- WCAG AA contrast ratios for gold/black text combinations.
- Focus-visible styles on navigation and interactive controls.
- Charts declare
role="img"and provide descriptivearia-labeltext.
app/
page.tsx # Home dashboard
standings/page.tsx # Leaderboard view
games/page.tsx # Career game log
sitemap.ts # Sitemap for search indexing
api/ # tracker, ingest, health endpoints
data/
leaders.json # Historical assist leader data
network-overrides.json # Manual network overrides by game date
components/
charts/ # AssistSparkline, chart config
LiveDataUpdater.tsx # Polls tracker API and refreshes on change during live windows
ShareTrackerButton.tsx # Clipboard share button used in header/footer
GameLogSection.tsx # Career game log table
StandingsTable.tsx # Top-10 leaderboard
RecentGamesTable.tsx # Recent game recap
NextGameCard.tsx # Upcoming/live game display
ProgressBar.tsx # Hero pace progress
PaceModule.tsx # Pace scenarios and required averages
... # SiteHeader, SiteFooter, Navigation, etc.
lib/
tracker.ts # Core ingestion + normalization logic
data-service.ts # Cached snapshot helper with live stats support
site.ts # Canonical URL helpers
metrics.ts # Derived stat calculations (pace, averages)
espn.ts # ESPN API helpers with caching and live stats
cache.ts # File-based cache for tracker data
network-overrides.ts # Loads data/network-overrides.json for manual corrections
team-names.ts # School-to-nickname mapping for display
hooks/useLiveData.ts # Optional hook for granular live data polling
.github/workflows/
update-stats.yml # GitHub Actions workflow for automated updates
public/data/tracker.json # Tracker data snapshot (committed to git)
scripts/
update-data.ts # Manual update script (used by GitHub Actions)
update-scenario-model-from-kalshi.ts # Refreshes March Madness probabilities from Kalshi + Polymarket markets
CLOUDFLARE_DEPLOYMENT.md- Cloudflare Pages deployment guideLIVE_UPDATES_QUICK_START.md- Quick guide to setting up live game updatesLIVE_UPDATES_SETUP.md- Detailed live updates documentationAUTOMATION_OPTIONS.md- Overview of automation approaches (GitHub Actions, Cloudflare Cron, etc.)
- Big Ten historical leaderboard toggle.
- Embeddable mini widget showing progress bar + core metrics.
- Automated social share image generation.
- Push notifications or webhooks when Smith climbs the leaderboard.
This project relies on publicly accessible ESPN and NCAA endpoints. Respect their terms of service, rate limits, and attribution requirements when extending or deploying the tracker.