Architecture Overview
General presentation
Racines is a PWA (Progressive Web App) companion for learning African languages. The application is organized around three roles (Player, Speaker, Admin) with dedicated routes and APIs.
Architecture diagram
Internet
│
▼
┌─────────────────────────────────────┐
│ Traefik │ ← Reverse proxy + TLS
│ (port 80/443) │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Next.js App │ ← Port 3000
│ (App Router, Server Components) │
│ │
│ ┌──────────┐ ┌────────────────┐ │
│ │ Pages │ │ API Routes │ │
│ │ / │ │ /api/* │ │
│ │ /[lang] │ │ │ │
│ │ /admin │ │ │ │
│ │ /speaker│ │ │ │
│ └──────────┘ └───────┬────────┘ │
└───────────────────────┬┴────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ PostgreSQL │ │ MinIO │
│ (Port 5432) │ │ (Port 9000) │
│ │ │ Bucket: audios │
│ languages │ │ (public-read) │
│ cards │ └─────────────────┘
│ items │ │
│ speakers │ │ Public URL
│ admins │ ▼
│ statistics │ ┌─────────────────┐
│ contact_msgs │ │ CDN / Client │
│ quiz_sessions │ │ (AUDIO_CDN_URL) │
└─────────────────┘ └─────────────────┘
Technology stack
| Layer | Technology | Role |
|---|---|---|
| Framework | Next.js 16 (App Router) | SSR, API Routes, routing |
| UI | React 19 + TypeScript | Components, state |
| Styling | Tailwind CSS 4 | Utility styles |
| Database | PostgreSQL 15 | Application data |
| ORM / DB Client | pg (node-postgres) |
Raw SQL queries with pool |
| Audio storage | MinIO (S3-compatible) | MP3 audio files |
| Audio processing | FFmpeg (fluent-ffmpeg) | Conversion, compression, normalization |
| Admin auth | PBKDF2 + JWT (jose) | Secure authentication |
| PWA | Service Worker + IndexedDB v3 | Offline mode |
| Charts | Recharts | Admin graphs |
| Excel import | xlsx | Parsing .xlsx files |
| Nodemailer | Contact form | |
| Reverse proxy | Traefik | TLS, routing, CORS |
Main data flows
1. Read flow (player)
Player (browser)
│
├─ SSR (Server Component) ──► PostgreSQL → cards + items
│
├─ Client hydration
│
└─ Audio playback ──────────► MinIO CDN (AUDIO_CDN_URL) direct
or fallback /api/audio/serve/[...path]
2. Audio upload flow (speaker)
Speaker (browser)
│
└─ POST /api/audio/upload
│
├─ Speaker code verification (speakers table)
├─ FFmpeg processing (mono, 96kbps MP3)
├─ Upload to MinIO (bucket: audios)
└─ UPDATE items SET audio_url = ... (PostgreSQL)
3. Offline flow (player)
Player installs the app
│
└─ Service Worker INSTALL
│
└─ PRECACHE_URLS (static assets)
Player downloads a language
│
└─ CACHE_PAGES message → Service Worker
│
├─ Step 1: Ping /api/health/live
├─ Step 2: GET /api/languages/[id]/content → IndexedDB
├─ Step 3: Fetch audios → cache racines-audio-cache-v3
├─ Step 4: Orphan cleanup
└─ Step 5: Cache HTML pages
Structural architecture decisions
No TTS (Text-to-Speech)
The application never uses synthetic voices. If an item has no native recording (audio_url IS NULL), the audio button is simply absent. Quality takes precedence over completeness.
Offline-first
Content is designed to work without a connection after download. The Service Worker is the cornerstone of this architecture.
Direct audio CDN
In production, audio is served directly from MinIO via AUDIO_CDN_URL — the Next.js server is not in the read path. The fallback proxy (/api/audio/serve/) is only used in development.
PostgreSQL with raw SQL
No ORM. Queries are written in native SQL via the pg pool. This provides full control over performance and complex aggregations.
Conditional Service Worker
The SW is disabled on /admin and /speaker to avoid cache conflicts in these stateful interfaces.
Source directory structure
src/
├── app/ # Next.js App Router
│ ├── page.tsx # Home page (/)
│ ├── layout.tsx # Root layout (AUDIO_CDN_URL injection)
│ ├── [language]/ # Player pages
│ │ ├── page.tsx # Language hub
│ │ ├── words/ # Word Challenge
│ │ ├── phrases/ # Phrase Challenge
│ │ └── revision/ # MCQ Revision
│ ├── admin/ # Admin interface (JWT-protected)
│ ├── speaker/ # Speaker interface
│ ├── contact/ # Contact form
│ └── api/ # API Routes
├── components/ # React components
│ ├── admin/ # Admin components
│ ├── revision/ # MCQ components
│ └── [shared]/ # Nav, Audio, Modals...
├── contexts/ # React contexts
│ ├── LanguageContext.tsx # Language unlocking, admin flag
│ └── SpeakerContext.tsx # Speaker auth
├── hooks/ # Custom hooks
│ └── useAuth.tsx # Admin auth hook
├── lib/ # Utilities and services
│ ├── postgres.ts # PostgreSQL pool
│ ├── auth.ts # PBKDF2, JWT
│ ├── auth-middleware.ts # Protection middleware
│ ├── audio-url.ts # Audio URL generation
│ ├── minio-client.ts # MinIO client
│ ├── offline-download.ts # Offline download orchestration
│ ├── offline-data.ts # Reading data from IndexedDB
│ ├── network-state.ts # Network circuit breaker
│ ├── statistics.ts # Event tracking
│ └── quizUtils.ts # MCQ logic
├── types/ # TypeScript types
│ └── index.ts # All business types
└── data/
└── quiz-distractors.ts # MCQ distractor data