Production SaaS for AI-powered chess coaching — real-time engine analysis, voice commentary, and globally compliant subscription billing.
Full-stack TypeScript SaaS with client-side Stockfish WASM (zero server compute) for live evaluation and native server-side Stockfish for deep post-game analysis.

- Pluggable multi-provider AI: Google Gemini, Anthropic Claude, OpenAI; ElevenLabs and OpenAI text-to-speech driving voice commentary.
- Dual-processor billing — Stripe for primary regions, Paddle as Merchant of Record for global VAT compliance — feeding a unified Subscription model.
- NextAuth v5 with Google and GitHub OAuth 2.0, signature-verified idempotent webhooks, Redis sliding-window rate limits for LLM cost control.
- Redis-cached active-game store with PostgreSQL archival for cross-device session resume; deployed on Railway via Docker.
Chess Coach is a single Next.js 16 app with a dual-engine setup: Stockfish WASM running in a Web Worker on the player's machine for live evaluation (zero server compute, instant response), and the native Stockfish binary server-side for deep post-game analysis where consistency matters. A pluggable AI provider facade (Gemini default, Anthropic Claude and OpenAI as drop-in alternatives) generates natural-language coaching, with ElevenLabs and OpenAI text-to-speech driving voice commentary across multiple coach personas. NextAuth v5 handles session-based auth + Google/GitHub OAuth. Postgres holds the immutable Game archive; Redis holds the hot-written ActiveGame layer.
Stockfish WASM in a Web Worker
Engine analysis at every move is expensive on the server and adds latency. Running Stockfish WASM in the user's browser means zero per-move server cost, instant evaluation, and infinite scale. The server-side `stockfish` package is reserved for post-game analysis where deterministic results matter.
Pluggable AI provider abstraction
Gemini default — its free tier keeps unit cost near zero, which makes coach-on-every-move UX viable. Claude and OpenAI are drop-in alternatives behind the same interface for A/B testing response quality and surviving provider outages.
Split ActiveGame and Game tables
ActiveGame is mutable, single-row-per-user, hot-written every move (Redis-cached). Game is the immutable historical record (PGN, accuracy, blunder counts) — written once on game completion. Splitting them prevents history queries from contending with active-game writes.
Stripe + Paddle dual checkout
Stripe is rejected in some regions where the audience actually lives. Paddle covers them as Merchant of Record (handles VAT/sales tax). The app exposes a single Subscription model; provider-specific webhook handlers normalize events into shared status transitions.
Webhook security model
Both Stripe and Paddle webhooks are signature-verified, then idempotency-keyed on the provider event ID. Sliding-window rate limits on AI/coach endpoints control LLM cost. Hand-rolled FEN/SAN validators block crafted board states from breaking the engine.