The Flashcard App is a modern web application designed for effective learning using the Spaced Repetition System (SRS). Built with Next.js 15, TypeScript, and Turso database, it provides users with a powerful platform for creating, managing, and studying flashcards.
What started as a quick project for my final general education exam turned into something much more ambitious. The app implements a proper SRS algorithm (borrowed heavily from Anki's approach), supports multiple languages, includes AI-powered flashcard generation, and even has a subscription system.
Honestly, it's probably over-engineered for what I initially needed, but where's the fun in building something simple?
The whole thing started because I needed to study for my final exams and wasn't particularly happy with the existing flashcard apps out there. Anki is powerful but feels ancient, and most modern alternatives either cost too much, are bloated with ads or lack the sophisticated spaced repetition that actually makes flashcards effective.
So naturally, instead of just using what was available, I decided to build my own. It was the perfect opportunity to procrastinate a little longer before I actually had to start studying.
Initially, this was meant to be a quick proof of concept - just enough to get through my exams. But as often happens with side projects, it grew legs and started walking on its own.
The first version was pretty rough. I had database logic scattered throughout components, inconsistent statistics calculations, and way too many console.log
statements that I forgot to remove.
Over time, I've been slowly refactoring the technical debt from that initial rush job. It's still not perfect (more on that later), but it's getting there.
As of version 1.10.1, the app includes:
It's deployed on Vercel and actually works pretty well. I use it regularly, which I guess is the ultimate test for any personal project.
The goal is to create a flashcard app that doesn't suck. Something that combines the sophistication of Anki with a modern, intuitive interface. I want it to be powerful enough for serious learners but simple enough that you don't need to learn how the software works itself first.
I'm currently working on some more advanced features like AI-powered flashcard generation coupled with a subscription model to support ongoing development costs. The idea is to keep the core features free while offering premium functionality for those who want it.
This is a pretty standard Next.js application with server-side rendering and server actions. I chose to keep everything in a single codebase rather than splitting into separate frontend and backend services, mainly because hosting is so much easier when you only have one thing to deploy.
The architecture looks something like this:
TXT1┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ 2│ Frontend │ │ Server Actions │ │ Database │ 3│ (React/Next) │◄──►│ (Next.js API) │◄──►│ (Turso) │ 4└─────────────────┘ └──────────────────┘ └─────────────────┘ 5 │ 6 ▼ 7 ┌──────────────────┐ 8 │ External APIs │ 9 │ (Stripe, AI) │ 10 └──────────────────┘
I tried to follow a pretty standard React pattern:
There's still some technical debt where I have database logic directly in components from the initial proof of concept, but I'm slowly cleaning that up.
Data flows through server actions, which is honestly one of my favorite things about Next.js 15. No need to set up API routes for everything - you can just call server functions directly from your components.
The pattern is simple: user interacts with UI, component calls server action, server action handles business logic and database operations, then the UI updates with new data.
I keep client-side state pretty minimal. Most data lives in the database and gets fetched fresh when needed. For the few things that need client-side state (like user preferences and animations), I use Zustand with localStorage persistence.
Users can create and organize flashcards into decks. Each deck has:
The deck creation process is straightforward - you fill out a form, and it creates a new deck.
There are three ways to create flashcards:
The bulk import was added because manually creating dozens of cards gets tedious fast. You just paste in a JSON array with your cards, and it processes them all at once.
This is where the magic happens. The app uses a spaced repetition algorithm based on SuperMemo-2. When you review a card, you rate it from 1-4:
The algorithm then calculates when you should see that card next, spacing out reviews over increasingly longer intervals.
The app tracks various statistics including daily review counts, success rates, learning streaks, time-of-day analysis, and cards by difficulty level.
I'll be honest - the statistics code was a nightmare for a while. I had inconsistent filtering logic scattered everywhere, which led to weird edge cases where the numbers didn't add up. It's mostly fixed now, but this is definitely an area where the initial proof-of-concept approach bit me.
Study sessions track your learning activity, including start and end times, number of cards reviewed, and whether the session was completed. There's some auto-save functionality to handle cases where you close the browser mid-session, though I'm not 100% confident it works perfectly in all edge cases.
I chose Turso because it's basically SQLite but hosted, which gives you the simplicity of SQLite with the convenience of not having to manage your own database server.
The AI features are functional but still being refined:
Current State:
What's Being Worked On:
The AI implementation was 100% generated by AI initially (ironic, I know), but it actually did a pretty good job. I've been slowly understanding and improving the code as I go.
Stripe integration is implemented for Pro subscriptions:
Current State:
What Needs Work:
The payment stuff works, but there are definitely edge cases I haven't fully tested. It's one of those areas where you don't really know how robust your implementation is until real money starts flowing through it.
The database schema is pretty straightforward:
Users & Auth (handled by NextAuth):
users
- Basic user informationaccounts
, sessions
, verificationTokens
- Auth-related tablesLearning Content:
decks
- Collections of flashcardsflashcards
- Individual cards with front/back contentcard_reviews
- Current state of each card's review schedulereview_events
- Historical record of all reviewsAnalytics:
study_sessions
- Tracking learning sessionsuser_preferences
- UI preferences and settingsBusiness:
subscriptions
- Stripe subscription dataThe relationships are pretty standard. Users have many decks, decks have many flashcards, flashcards have many review events, and users have review history for cards.
I've added indexes on the most commonly queried fields:
Performance is generally good, though I haven't done serious load testing. SQLite is pretty fast for this kind of workload.
Database migrations are handled by Drizzle. When you want to add a field to a table, you extend the schema in code and generate new SQL migration files. Then you run the migration to safely apply changes to the actual database.
It's a clean approach, though I did spend some frustrated hours debugging migration issues early on when I didn't fully understand how Drizzle worked.
Authentication uses NextAuth.js with email-only login (no passwords). Users get a magic link sent to their email, click it, and they're logged in. It's simple and secure - no passwords to forget or leak.
Sessions are JWT-based and handled entirely by NextAuth. The session includes the user ID, which is all we really need for most operations.
I've implemented rate limiting using Upstash Redis to prevent abuse:
The rate limiting saved me when I accidentally created an infinite loop during development that was sending hundreds of emails.
The app includes standard security headers like CSP (Content Security Policy), X-Frame-Options, X-XSS-Protection, and HSTS (HTTP Strict Transport Security). These are configured in the middleware and help protect against common web vulnerabilities.
User data is isolated by user ID in all database queries. Server actions verify the user's session before performing any operations. It's not bulletproof, but it covers the basics.
After some research (prompting AI) I went with the SuperMemo-2 algorithm. I'm not skilled enough yet to implement SRS algorithms cleanly from scratch, so I had AI generate most of this initially (and it did a surprisingly good job).
When you review a card, the algorithm calculates:
The ease factor adjusts based on your rating. Rating 1 (Again) decreases ease and resets the interval, Rating 2 (Hard) slightly decreases ease with a modest interval increase, Rating 3 (Good) maintains ease with normal interval calculation, and Rating 4 (Easy) increases ease with a longer interval.
New cards start with a 1-day interval. As you successfully review them, the intervals get longer: 1 day → 3 days → 8 days → 20 days → 48 days, etc.
The exact intervals depend on the ease factor and your performance on that specific card.
The system tracks various metrics to help users understand their progress:
Some of these calculations were buggy for a while due to inconsistent filtering logic, but they're mostly cleaned up now.
The app supports English and German using next-intl. The setup is pretty standard - you have message files for each language, and the library handles the rest.
Currently supports:
Adding more languages is just a matter of creating new translation files and updating the language selector.
User language preferences are stored in the database for authenticated users, and in cookies for guests. The locale is determined in this order:
Translations are stored in JSON files under /messages/
. When I need to add new text, I add it to the English file and then create the German translation.
It's a manual process right now, which doesn't scale great, but it works for the current size of the app.
The UI uses a consistent design system built on top of shadcn/ui components and Tailwind CSS. I went for a clean, modern look that doesn't get in the way of learning.
The design philosophy is “functional first, pretty second.” It needs to work well before it needs to look amazing.
The app works on mobile, tablet, and desktop. The flashcard interface is optimized for touch interactions on mobile devices.
I spent way too much time getting the card flip animations to work properly on different screen sizes, but it was worth it for the satisfying interaction.
Basic accessibility features are implemented:
There's definitely room for improvement here, but it covers the essentials.
The app includes subtle animations powered by Framer Motion:
Users can disable animations in their preferences if they prefer a more static experience.
Supports light and dark themes, with system preference detection. Theme preference is stored in user settings and synced across devices.
Next.js handles most of the optimization automatically:
I've optimized the most common queries:
There are still some areas where I could optimize further, particularly in the statistics calculations.
The app generally scores well on Core Web Vitals, though there's always room for improvement. The main performance bottleneck is usually the statistics dashboard when you have a lot of review history.
There are still some edge cases I haven't fully resolved:
The biggest issue is leftover technical debt from the initial proof of concept:
I'm slowly working through these issues, but it's the kind of thing you notice every time you touch anything statistics-related.
Short Term:
Medium Term:
Long Term:
The current architecture should handle a reasonable number of users, but there are areas that would need attention for serious scale:
To run the app locally:
Bash1# Clone the repository 2git clone https://github.com/Fx64b/learn.git 3cd learn 4 5# Install dependencies 6pnpm install 7 8# Set up environment variables 9cp .env.local.example .env.local 10# Fill in your environment variables 11 12# Run database migrations 13pnpm db:migrate 14 15# Start the development server 16pnpm dev
If you want to contribute (and help me clean up the technical debt), here are some guidelines:
If you find bugs or have feature requests, please open an issue on GitHub. Include as much detail as possible - it makes fixing things much easier.
I'm open to feature requests, though keep in mind this is primarily a personal project. Features that align with the goal of “create a flashcard app that doesn't suck” are most likely to be implemented.
This project is open source under the MIT License. Feel free to use it, modify it, or learn from it. If you do use it for something cool, I'd love to hear about it.