← blog
July 10, 2026·2 min·72 views

FSRS spaced repetition + native Stockfish in React Native

learning in publicreact nativeexpoalgorithmes
FSRS spaced repetition + native Stockfish in React Native

I wanted a chess app that doesn't just throw puzzles at you, but one that learns from you and makes you review at the right moment. Two building blocks make that possible: FSRS spaced repetition and a native Stockfish engine. Here's how I wired them into a React Native app.

FSRS so you're not reviewing at random

The Move Trainer runs on FSRS (Free Spaced Repetition Scheduler), the same kind of algorithm behind modern Anki cards. Every opening move or tactical pattern becomes a "card": depending on whether you recall it easily or not, its next review date gets recomputed. The algorithm lives in a dedicated packages/chess package, decoupled from the UI, which lets me test it without launching the app.

Stockfish native, not in a WebView

Most mobile apps run Stockfish inside a WebView. I wanted to avoid that: the engine runs natively through an Expo Dev Client (expo prebuild). In dev, a mock engine takes over as long as the native module isn't built yet, driven by an extra.useNativeEngine flag — so I can code the UI without depending on Xcode every single time.

Where the data comes from

  • chess.com PubAPI: stats and games, no OAuth.
  • Lichess API: OAuth2, with the Opening Explorer proxied through my API so tokens never get exposed on the mobile side.
  • The Lichess puzzle database (~5.9M puzzles, CC0 license), of which I import a subset server-side.

The architecture that holds up

The backend is a hexagonal NestJS (Domain / Application / Infrastructure / Presentation), and the domain is described exactly once via shared Zod schemas (packages/types). The GitHub Actions CI is path-filtered: one workflow per app, triggered only when the relevant code changes — the API deploys to the VPS, the mobile app ships to EAS.

What I'm learning

Getting a native module to run cleanly in Expo takes discipline (Xcode 15+, JDK 17), but the mock fallback was the best call: it decouples my dev pace from the native build chain.

Ever integrated a native module (game, ML, audio) into an Expo app? What was your biggest headache? Tell me about it.