Si t'as déjà révisé un examen la veille au soir, tu sais à quel point c'est galère de tout retenir. Les flashcards, c'est l'un des outils les plus efficaces parce qu'elles font bosser le rappel actif — ton cerveau doit aller chercher l'info au lieu de la relire passivement.
Créer une app de flashcards digitale, c'est mélanger dev web moderne et psychologie de l'apprentissage. Dans ce guide, on va voir comment bâtir une application full-stack avec Next.js pour le front/back et MongoDB pour stocker les données.
Pourquoi une App de Flashcards ?
Les versions numériques ont pas mal d'avantages sur le papier :
- **Portabilité** : tes decks sont accessibles partout avec une connexion
- **Répétition espacée** : des algorithmes optimisent les moments de révision
- **Support média** : images, audio, mise en forme
- **Suivi de progression** : analytics sur tes perfs d'apprentissage
Cas Concrets
Avant de plonger dans le code, quelques scénarios où l'app brille :
1. **Étudiants en médecine** : flashcards d'anatomie détaillées avec images
2. **Apprenants de langues** : decks de vocabulaire avec audio de prononciation
3. **Certifications pro** : organiser des concepts techniques complexes
Stack Technique
On utilise :
- **Next.js 14** (framework React avec SSR)
- **MongoDB Atlas** (base cloud)
- **Mongoose** (modélisation objets MongoDB)
- **Tailwind CSS** (styling)
Pourquoi cette stack ? Next.js gère frontend et routes API, ce qui simplifie le déploiement. Le schéma flexible de MongoDB colle bien aux structures variables de flashcards.
Setup du Projet
D'abord, crée le projet Next.js :
```bash
npx create-next-app@latest flashcard-app
cd flashcard-app
npm install mongoose mongodb
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
Configure Tailwind dans `tailwind.config.js` :
```javascript
/** @type {import('tailwindcss').tailwind.config} */
module.exports = {
content: [
"./app/**/*.{js,jsx,ts,jsx}",
"./pages/**/*.{js,jsx,ts,jsx}",
"./components/**/*.{js,jsx,ts,jsx}",
],
theme: {
extend: {},
},
plugins: [],
}
```
Ajoute dans `app/globals.css` :
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Connexion à la Base
Crée `lib/mongodb.js` :
```javascript
import { MongoClient } from 'mongodb'
const uri = process.env.MONGODB_URI
const options = {
useNewUrlParser: true,
useUnifiedTopology: true,
}
let client
let clientPromise
if (!process.env.MONGODB_URI) {
throw new Error('Ajoute ton URI MongoDB dans .env.local')
}
if (process.env.NODE_ENV === 'development') {
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
}
clientPromise = global._mongoClientPromise
} else {
client = new MongoClient(uri, options)
clientPromise = client.connect()
}
export default clientPromise
```
Modèle Flashcard
Dans `models/Flashcard.js` :
```javascript
import mongoose from 'mongoose'
const flashcardSchema = new mongoose.Schema({
question: {
type: String,
required: true,
},
answer: {
type: String,
required: true,
},
deck: {
type: String,
required: true,
},
tags: [String],
createdAt: {
type: Date,
default: Date.now,
},
})
export default mongoose.models.Flashcard || mongoose.model('Flashcard', flashcardSchema)
```
Routes API
Les routes API Next.js gèrent notre backend. Crée `pages/api/flashcards/index.js` :
```javascript
import dbConnect from '../../../lib/dbConnect'
import Flashcard from '../../../models/Flashcard'
export default async function handler(req, res) {
const { method } = req
await dbConnect()
switch (method) {
case 'GET':
try {
const flashcards = await Flashcard.find({})
res.status(200).json(flashcards)
} catch (error) {
res.status(400).json({ success: false })
}
break
case 'POST':
try {
const flashcard = await Flashcard.create(req.body)
res.status(201).json(flashcard)
} catch (error) {
res.status(400).json({ success: false })
}
break
default:
res.status(400).json({ success: false })
break
}
}
```
Composants Frontend
Formulaire de création `components/FlashcardForm.js` :
```javascript
import { useState } from 'react'
export default function FlashcardForm({ onSubmit }) {
const [question, setQuestion] = useState('')
const [answer, setAnswer] = useState('')
const [deck, setDeck] = useState('')
const handleSubmit = async (e) => {
e.preventDefault()
const response = await fetch('/api/flashcards', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, answer, deck })
})
if (response.ok) {
const newFlashcard = await response.json()
onSubmit(newFlashcard)
setQuestion('')
setAnswer('')
setDeck('')
}
}
return (
)
}
```
Mode Révision
`components/StudyMode.js` pour l'entraînement au rappel actif :
```javascript
import { useState, useEffect } from 'react'
export default function StudyMode({ flashcards }) {
const [currentIndex, setCurrentIndex] = useState(0)
const [showAnswer, setShowAnswer] = useState(false)
const [shuffledCards, setShuffledCards] = useState([])
useEffect(() => {
setShuffledCards([...flashcards].sort(() => Math.random() - 0.5))
}, [flashcards])
const nextCard = () => {
setShowAnswer(false)
if (currentIndex < shuffledCards.length - 1) {
setCurrentIndex(currentIndex + 1)
}
}
const prevCard = () => {
setShowAnswer(false)
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1)
}
}
if (!shuffledCards.length) return
Aucune flashcard dispo
const card = shuffledCards[currentIndex]
return (
Question :
{card.question}
{showAnswer && (
Réponse :
{card.answer}
)}
onClick={() => setShowAnswer(!showAnswer)}
className="bg-gray-500 text-white px-4 py-2 rounded"
>
{showAnswer ? 'Cacher la réponse' : 'Voir la réponse'}
{currentIndex > 0 && (
)}
{currentIndex < shuffledCards.length - 1 && (
)}
Carte {currentIndex + 1} sur {shuffledCards.length}
)
}
```
Page Principale
`app/page.js` :
```javascript
import { useState, useEffect } from 'react'
import FlashcardForm from '../components/FlashcardForm'
import StudyMode from '../components/StudyMode'
export default function Home() {
const [flashcards, setFlashcards] = useState([])
const [activeView, setActiveView] = useState('create')
const [selectedDeck, setSelectedDeck] = useState('')
useEffect(() => {
fetchFlashcards()
}, [])
const fetchFlashcards = async () => {
const response = await fetch('/api/flashcards')
const data = await response.json()
setFlashcards(data)
}
const addFlashcard = (newFlashcard) => {
setFlashcards([newFlashcard, ...flashcards])
}
const filteredCards = selectedDeck
? flashcards.filter(card => card.deck === selectedDeck)
: flashcards
const decks = [...new Set(flashcards.map(card => card.deck))]
return (
Flashcard Study App
onClick={() => setActiveView('create')}
className={`px-4 py-2 mr-2 rounded ${
activeView === 'create' ? 'bg-blue-500 text-white' : 'bg-white'
}`}
>
Créer des Flashcards
onClick={() => setActiveView('study')}
className={`px-4 py-2 rounded ${
activeView === 'study' ? 'bg-blue-500 text-white' : 'bg-white'
}`}
disabled={flashcards.length === 0}
>
Mode Révision
{activeView === 'create' && (
)}
{activeView === 'study' && (
value={selectedDeck}
onChange={(e) => setSelectedDeck(e.target.value)}
className="w-full p-2 border rounded"
>
{decks.map(deck => (
))}
)}
{activeView === 'study' &&
Tes Decks
{decks.length === 0 ? (
Pas de deck encore. Crée ta première flashcard au-dessus !
) : (
{deck} ({flashcards.filter(c => c.deck === deck).length} cartes)
{decks.map(deck => (
))}
)}
)
}
```
Points pour la Prod
Pour la production, pense à ces améliorations :
1. **Authentification** : comptes utilisateurs avec NextAuth.js
2. **Répétition espacée** : implémenter des algos type SM-2
3. **Média riche** : support images et audio dans les flashcards
4. **Mode hors-ligne** : service workers pour capacités PWA
Déploie sur Vercel (optimisé pour Next.js) ou équivalent. Configure ton cluster MongoDB Atlas et les variables d'env.
Conclusion
Cette app montre les concepts de base tout en restant pratique. Tu as maintenant une base extensible selon tes besoins. Que ce soit pour les langues, les examens ou le pro, les flashcards digitales offrent une solution efficace et personnalisable.
Commence simple, itère vite, et focalise-toi sur l'expérience utilisateur. Bonne chance pour ton apprentissage !
FAQ
Q: Faut-il connaître MongoDB avant ?
R: Ça aide mais c'est pas obligatoire. Le modèle documentaire est intuitif une fois qu'on pige les structures type JSON.
Q: Peut-on ajouter des images ?
R: Oui ! Étends le schéma avec des URLs d'images et modifie les composants de formulaire pour gérer l'upload.
Q: Comment implémenter la répétition espacée ?
R: Stocke l'historique de révision avec timestamps, puis calcule les intervalles optimaux selon la performance de rappel. Des libs comme `supermemo` peuvent aider pour les algos.
Q: Ça marche sur mobile ?
R: Avec des frameworks CSS responsives comme Tailwind, oui. Pense à ajouter des features PWA pour une expérience native.
Comments (0)
No comments yet. Be the first to comment!
Leave a Comment