Skip to content

Repository files navigation

Kevin Mok's Chess Analytics

Recruiter-facing chess analytics site built as a static-data Next.js App Router project. The UI is fed by a PGN ingest pipeline that normalizes Chess.com games into derived JSON, then renders /, /highlights, /games, /games/[id], and /insights without runtime PGN parsing or a separate backend.

Live demo

Project purpose

  • Turn a raw Chess.com export into a polished frontend portfolio artifact.
  • Demonstrate typed data modeling, App Router composition, charting, replay controls, and recruiter-facing storytelling.
  • Keep the runtime simple: pages read derived JSON only.

Routes

  • /: overview, hero, KPI cluster, spotlight strip, recruiter framing, Highlight Games, recent games, and a recent-window ELO preview that mirrors the Elo Over Time chart language
  • /highlights: Highlight Games index driven by repo-local PGN/analysis snapshots
  • /highlights/[slug]: replay/detail page for each Highlight Game
  • /games: searchable and filterable All Games view with URL-backed params
  • /games/[id]: replay/detail page with move navigation and material balance
  • /insights: Elo Over Time view plus splits, streaks, heatmap, and breakdowns, with a horizontally scrollable mobile chart that preserves comfortable spacing and leads the filter stack on phones

Stack

  • Next.js App Router
  • React 19
  • TypeScript (strict)
  • Tailwind CSS via app/globals.css
  • Recharts
  • react-chessboard
  • chess.js
  • date-fns
  • zod
  • motion
  • next/font/local

PGN source and identity mapping

  • Canonical source PGN: pgn/chess_com_games_2026-03-15_combined.pgn
  • Public display identity: Kevin Mok
  • Source-data username: SoloPistol
  • Identity mapping lives in lib/identity.ts

The ingest pipeline preserves the source username where needed for parsing, then replaces public-facing text with the display identity in normalized output.

Ingest and rebuild workflow

  1. Raw PGN is parsed by scripts/ingest-pgn.ts.
  2. Curated Highlight Games can be refreshed from the external chess workspace with scripts/ingest-highlights.ts.
  3. Highlight Game source snapshots are stored under content/highlights/ after ingest.
  4. Parsing and normalization live in lib/pgn.ts.
  5. Highlight Game README parsing, manifest generation, and markdown excerpt parsing live in lib/highlights.ts.
  6. Summary analytics live in lib/analytics.ts.
  7. Derived JSON is written to data/derived/:
    • games.json
    • summary.json
    • openings.json
    • highlights.json
  8. Pages load only through lib/data.ts.

data/derived/ is ignored from source control and should be rebuilt from the canonical PGN when the export changes.

pnpm ingest:highlights reads /home/kevin/Documents/chess/README.md by default, matches the Highlight Game rows to PGNs under /home/kevin/Documents/chess/games plus analysis markdown under /home/kevin/Documents/chess/analysis, updates content/highlights/, and rewrites data/derived/highlights.json. Set CHESS_HIGHLIGHTS_ROOT if the external chess workspace lives elsewhere.

Local commands

pnpm ingest:pgn
pnpm ingest:highlights
pnpm test
pnpm typecheck
pnpm lint
pnpm build
pnpm dev

Live site update script

Run scripts/update-live-site.sh on the production checkout to pull the latest code, install locked dependencies, rebuild the app, and restart the server on 127.0.0.1:3003.

./scripts/update-live-site.sh

What it does:

  • Refuses to deploy from a dirty git checkout.
  • Acquires log/live-site.lock so overlapping restart runs fail fast instead of racing each other.
  • Uses git pull --ff-only to avoid merge commits on the server.
  • Runs pnpm install --frozen-lockfile before pnpm run build.
  • Stops existing repo-local pnpm start / pnpm exec next start runners before restarting, including their child processes.
  • Restarts the live server with nohup pnpm start -- --hostname 127.0.0.1 --port 3003.
  • Writes the managed PID to log/live-site.pid and app output to log/live-site.log.

If port 3003 is already held by a process that was not started from this repo checkout, the script refuses to kill it and exits with a clear error so it does not take down an unrelated service.

For manual production starts, use the normal package script:

pnpm start -- --hostname 127.0.0.1 --port 3003

The repo wraps next start so pnpm's forwarded -- delimiter does not get misinterpreted as a project directory. That wrapper also holds a host/port runtime lock under /tmp, so overlapping pnpm start launches fail before they can race into EADDRINUSE.

Architecture summary

  • Core domain contracts stay in types/chess.ts, lib/pgn.ts, lib/analytics.ts, and lib/data.ts.
  • UI-facing pure helpers live in lib/:
  • Components are grouped by route area under components/home, components/games, components/detail, components/insights, with shared pieces under components/ui.
  • Client components are intentionally limited to:
    • hero micro-interactions
    • URL-interactive filters
    • charts
    • replay controls

Validation notes

  • The local canonical PGN currently produces 125 games with a 65 / 54 / 6 record after merging in the March 20 Chess.com export and deduping overlapping games.
  • pnpm build uses webpack plus bundled local fonts so the production build works in a network-restricted environment.
  • Next's duplicate build-time TypeScript validation is disabled in next.config.ts; use pnpm typecheck as the authoritative type gate.

Social preview checklist

  • Emit og:title, og:description, og:url, og:image, og:type, and a canonical URL from the same shared metadata source.
  • Use absolute https:// URLs for the canonical URL and all social images; never leak localhost, http://, or a temporary deployment host.
  • Keep the public share URL, canonical URL, and og:url on the same host so third-party crawlers do not have to reconcile mismatched origins.
  • Prefer stable social-card dimensions and include og:image:width, og:image:height, og:image:alt, and og:image:type.
  • If a platform keeps showing an old preview after deployment, change the image URL itself because many scrapers cache images by URL.

Known limitations

  • Opening labels are heuristic signatures, not authoritative ECO classifications.
  • There is no engine evaluation, annotation, or move-quality scoring.
  • /games (All Games) and /insights (Elo Over Time) are query-param driven and server-render filtered, so they are not pre-rendered for every filter combination.
  • The current dataset is small enough to keep the UI simple; no virtualization or export flows are included.

Future enhancements

  • Add richer opening taxonomy or ECO lookup when a trustworthy mapping source is available.
  • Add downloadable insight snapshots or per-route OG images.
  • Add richer game annotations, notable-moment heuristics, or tactical motif summaries.

About

Next.js portfolio app that turns Chess.com PGN exports into interactive game replays, filters, and performance insights.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages