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.
- 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.
/: 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
- Next.js App Router
- React 19
- TypeScript (strict)
- Tailwind CSS via
app/globals.css - Recharts
react-chessboardchess.jsdate-fnszodmotionnext/font/local
- 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.
- Raw PGN is parsed by
scripts/ingest-pgn.ts. - Curated Highlight Games can be refreshed from the external chess workspace with
scripts/ingest-highlights.ts. - Highlight Game source snapshots are stored under
content/highlights/after ingest. - Parsing and normalization live in
lib/pgn.ts. - Highlight Game README parsing, manifest generation, and markdown excerpt parsing live in
lib/highlights.ts. - Summary analytics live in
lib/analytics.ts. - Derived JSON is written to
data/derived/:games.jsonsummary.jsonopenings.jsonhighlights.json
- 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.
pnpm ingest:pgn
pnpm ingest:highlights
pnpm test
pnpm typecheck
pnpm lint
pnpm build
pnpm devRun 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.shWhat it does:
- Refuses to deploy from a dirty git checkout.
- Acquires
log/live-site.lockso overlapping restart runs fail fast instead of racing each other. - Uses
git pull --ff-onlyto avoid merge commits on the server. - Runs
pnpm install --frozen-lockfilebeforepnpm run build. - Stops existing repo-local
pnpm start/pnpm exec next startrunners 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.pidand app output tolog/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 3003The 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.
- Core domain contracts stay in
types/chess.ts,lib/pgn.ts,lib/analytics.ts, andlib/data.ts. - UI-facing pure helpers live in
lib/:lib/game-filters.tsfor filtering, sorting, pagination, and derived optionslib/material.tsfor material-balance serieslib/formatters.tsfor compact display formatting
- Components are grouped by route area under
components/home,components/games,components/detail,components/insights, with shared pieces undercomponents/ui. - Client components are intentionally limited to:
- hero micro-interactions
- URL-interactive filters
- charts
- replay controls
- The local canonical PGN currently produces
125games with a65 / 54 / 6record after merging in the March 20 Chess.com export and deduping overlapping games. pnpm builduses 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; usepnpm typecheckas the authoritative type gate.
- 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 leaklocalhost,http://, or a temporary deployment host. - Keep the public share URL, canonical URL, and
og:urlon 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, andog:image:type. - If a platform keeps showing an old preview after deployment, change the image URL itself because many scrapers cache images by URL.
- 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.
- 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.