Analyze Chess.com opponents — find their opening weaknesses, time trouble patterns, and exploitable habits. Also rank candidate lines by practical difficulty (engine + human data).
- Node.js 20 or later
- npm 9+
npm installCopy .env.local.example to .env.local and set DATABASE_URL (Prisma loads at startup; use your Postgres URL or create a local DB). No Redis or worker needed for opponent analysis; SQLite cache is created automatically.
cp .env.local.example .env.local
# Edit .env.local: set DATABASE_URL to e.g. postgresql://user:password@localhost:5432/chess_prep
npx prisma migrate deploy # create tables
npm run devOpen http://localhost:3000. Enter a Chess.com username to analyze openings, time management, and weaknesses.
If you see "Environment variable not found: DATABASE_URL", add DATABASE_URL to .env.local (see .env.local.example).
To use Line difficulty (http://localhost:3000/analyze-position) you need PostgreSQL, Redis, and the worker.
-
Env — copy
.env.local.exampleto.env.localand set:DATABASE_URL— PostgreSQL connection stringREDIS_URL— optional; defaults toredis://localhost:6379
-
PostgreSQL — create a database (e.g.
chess_prep) and set its URL inDATABASE_URL. -
Redis — start Redis (e.g.
redis-server). Default:redis://localhost:6379. -
Apply migrations:
npx prisma migrate deploy
-
Start the app:
npm run dev
-
In a separate terminal, start the worker (processes line-analysis jobs):
npm run worker
Then open http://localhost:3000/analyze-position, enter a FEN, and click Analyze position. The worker will run the analysis; when it finishes, ranked lines appear on the page.
Human move stats (used for opponent move distribution, line expansion, and trap detection) can be served from a local HTTP service instead of the Lichess Explorer API.
- Config: Set
FEN_MOVE_SERVICE_URLin.env.local(e.g.http://localhost:8080). When set, the app uses only the local service and does not call the Lichess Explorer API for this data. When unset or empty, it falls back to Lichess. - Endpoint:
GET /query?fen=<url-encoded FEN>&bucket=<e.g. 1600-1800>. FEN is normalized (6-field, trimmed) as in the rest of the app; bucket format is the same (e.g.1600-1800). - Response shape: JSON
{ "moves": [ { "move": "<uci>", "games": <number>, "winrate": <0-1> }, ... ] }— matchesGetHumanMovesResultso the app uses the same types (HumanMoveStat, etc.). Winrate is for the side to move in [0, 1].
Example: run the local service (e.g. from the lichess-move-db repo or equivalent):
cargo run --release -- serve --db ./data/fen_move.db --bind 127.0.0.1:8080Then set FEN_MOVE_SERVICE_URL=http://localhost:8080 in .env.local.
Enter any Chess.com username and the tool fetches their recent games, then runs four analyses:
| Section | What it tells you |
|---|---|
| Player Overview | Ratings, overall record, win rate by colour, recent form |
| Opening Repertoire | Interactive opening tree with win rates for White and Black |
| Time Management | Average clock curve, time-trouble frequency, flag rate, time spent by game phase |
| Weakness Report | Auto-detected weaknesses and strengths with severity, stats, and recommendations |
- Chess.com API — player profiles, stats, and game archives
- Human move stats — optional local FEN move service (
FEN_MOVE_SERVICE_URL); if not set, Lichess Opening Explorer is used for population move frequencies - Lichess Cloud Eval / Chess-API.com — Stockfish position evaluations
Results are cached in a local SQLite database so repeated lookups are fast.
| Command | Description |
|---|---|
npm run dev |
Start dev server (Turbopack) |
npm run build |
Production build |
npm start |
Start production server |
npm run worker |
Start BullMQ worker (line-analysis jobs) |
npm run lint |
Run ESLint |
npm run lint:fix |
Auto-fix lint issues |
npm run format |
Format all files with Prettier |
npm run format:check |
Check formatting without writing |
npm run type-check |
Run TypeScript type checker |
npm test |
Run tests once |
npm run test:watch |
Run tests in watch mode |
npm run test:coverage |
Run tests with coverage report |
- Framework — Next.js 16 (App Router)
- Language — TypeScript 5 (strict mode)
- UI — React 19, Tailwind CSS 4, Recharts, react-chessboard
- Databases — SQLite (better-sqlite3) for opponent-analysis cache; PostgreSQL (Prisma) for line-analysis cache and results
- Queue & cache — Redis + BullMQ for async line-analysis jobs and caching
- Chess — chess.js for PGN parsing and position handling
- Testing — Vitest
- Linting — ESLint (next/core-web-vitals + TypeScript), Prettier
- CI — GitHub Actions
src/
├── app/ # Next.js App Router pages & API routes
│ ├── api/ # REST endpoints
│ │ ├── analyze-position/ # POST — enqueue line-analysis job
│ │ ├── job/[id]/ # GET — job status
│ │ ├── line-analysis/ # GET — lines by root FEN
│ │ ├── player/[username]/ # profile, games, analysis
│ │ ├── openings/explore/ # Lichess explorer
│ │ ├── prep/suggest/ # Prep suggestions
│ │ └── eval/ # Engine eval
│ ├── analyze/[username]/ # Opponent analysis dashboard
│ ├── analyze-position/ # Line difficulty page
│ └── page.tsx # Landing page
├── components/
│ ├── analyze-position/ # FEN input, job status, line results
│ ├── opening-repertoire/ # Board, move explorer, prep
│ ├── PlayerOverview.tsx
│ ├── TimeAnalysis.tsx
│ ├── WeaknessReport.tsx
│ └── ...
└── lib/
├── analysis/ # Pure analysis (openings, time, weaknesses, metrics)
├── engine/ # Multi-PV analysis + PositionCache
├── lichess/ # Explorer + getHumanMoves
├── queue/ # BullMQ queue + line-analysis processor
├── cache.ts # Redis get/set + key helpers
├── config.ts
├── prisma.ts # Prisma client (PostgreSQL)
├── db.ts # SQLite (opponent cache)
├── validation.ts
├── chess-com.ts
├── lichess.ts
└── types.ts
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes — the pre-commit hook will lint and format automatically
- Run tests (
npm test) and type-check (npm run type-check) - Commit and push
- Open a Pull Request