A GRE vocabulary trainer with 1,100 words, AI-generated sentences, and a spaced repetition algorithm that adapts to what you actually know. Built for my girlfriend in the time it took her to go to spin class.
My girlfriend is studying for the GRE. She had downloaded a list of ~1,100 words and was working on typing them all into flashcards. She off-handedly remarked that she wished Quizlet didn't charge for their service because all she could get was flashcards with ads.
My ears perked up. “What, exactly, would your dream study platform look like?” I asked. She was hesitant at first to tell me the full scope — she thought it would be a lot of work. But by the time she had gone to spin and come back, her site was live.

Claude generates fresh sentences for every question. Three question types — single-blank completion, double-blank completion, and definition matching. The sentences aren't stored and replayed. Each time you encounter a word, the context is different, which forces actual comprehension instead of recall.
The core philosophy: users experience “random” questions, but the algorithm is quietly weighted. It's based on SM-2 (the spaced repetition algorithm behind Anki). Words due for review appear more often. Words you're struggling with get prioritized. Words you've mastered fade into the background. The user just sees an endless stream of questions — the optimization happens underneath.
There's no “start study session” button. You open the app, answer a question, close it. Come back in five minutes or five hours. The system tracks everything in the background — accuracy rates, answer times, mastery levels — without imposing structure on the user.
A full analytics dashboard shows activity heatmaps, accuracy trends over time, struggling words, recently mastered words, and aggregate study stats.
┌─────────────────────────────────────────────────┐
│ Hetzner VPS │
│ │
│ ┌───────────┐ ┌─────────────┐ ┌────────┐ │
│ │ Caddy │ │ Express │ │ SQLite │ │
│ │ :443 │──►│ :3000 │──►│ (WAL) │ │
│ │ HTTPS │ │ API │ │ │ │
│ └───────────┘ └──────┬──────┘ └────────┘ │
│ │ │
│ ┌──────────────────────┘ │
│ │ PM2 (process manager) │
│ └──────────────────────────────────────────────│
│ │
└───────────────────┬─────────────────────────────┘
│
┌─────────────┼──────────────┐
▼ ▼ ▼
Firebase Auth Claude API React SPA
(user mgmt) (sentences) (Vite build)Monorepo with three workspaces: client, server, and shared types. The Express API handles question generation (delegating to Claude), answer validation, progress tracking, and admin operations. The React frontend is a Vite SPA served by Caddy alongside the API.
SQLite runs in WAL mode for concurrent read performance. The database stores the full word corpus, per-user progress data, question history, and a pre-generated questions corpus for faster response times during study.

I had her list of words but no definitions. My first thought was to download a dictionary file. I asked Claude what options were out there and in our conversation it off-handedly mentioned that some dictionaries were better suited to “GRE-style definitions” than others. I had no idea what this meant, so I probed deeper. As it turns out — funny, right? Shows how much pursuit I have in higher education — GRE definitions are at times very different from the first dictionary definition. For example, brook as “to tolerate” not “a small stream.” So I used Opus 4.5 to generate all the definitions. Cost me about $2.
There was a bug in fill-in-the-blank — the app wasn't choosing all answer choices with the same part of speech. My girlfriend reported it but said it wasn't urgent, again thinking it would take time. So I had her describe her desired functionality, opened Claude Code on my iPhone and had it make the fix. I created a PR in the app, switched to the GitHub app and merged it. I logged into my VPS via Termius on iOS, pulled the changes, and ran a pm2 restart. The changes were live about five minutes after she made the bug report — and I didn't do a thing except orchestrate it.
This is a single-VPS deployment with one user. I already had the Hetzner VPS — that's one reason over Vercel. The backend also needs persistent state (SQLite), and long-lived connections (Claude API streaming). A $5/month VPS with Caddy for automatic HTTPS and PM2 for process management keeps everything running with minimal overhead.
I'd generate one question ahead. Right now when you submit a question, you're on a loading screen while the API calls Sonnet 4.5 to generate the next one. If I just had it generate one more question in advance, you'd never see a loading screen.
The other thing I'd do — since I'm already storing the generated sentences — is make it multi-user and subscription-based. I'd design the architecture so that new users first digest the pre-generated questions before their LLM calls start generating new ones. As existing users generate new questions, those get propagated to all other users. This would cut operational costs significantly — exponentially as users grew.