The frontend for Nihongo Master — a minimal, text-first way to drill Japanese for the JLPT. A marketing/content site (landing, about, docs, blog, changelog, learn, contribute) sits in front of the game: a spaced-repetition Review (FSRS) plus four free-practice modes — Kana, Kanji, Vocabulary, and Listening.
| Framework | Next.js 16 (App Router, Turbopack, RSC) |
| Language | TypeScript 5 — strict mode |
| Styling | Tailwind CSS v4 (CSS @theme, no JS config) + PostCSS |
| State | Zustand (persisted JLPT level) |
| Spaced repetition | FSRS via ts-fsrs, stored in IndexedDB (dexie) — client-side, no account |
| Content | MDX in /content (next-mdx-remote, gray-matter) |
| Fonts | Self-hosted Helvetica Neue via next/font/local |
| Design | Strict monochrome (grayscale + black/white), text-first |
| License | MIT (code) |
git clone https://github.com/Nihongo-Master/Application-Face.git
cd application-face
npm install
npm run devThe dev server runs at http://localhost:3000 with Turbopack.
| Command | Description |
|---|---|
npm run dev |
Development server (Turbopack) on localhost:3000 |
npm run build |
Production build → .next/ |
npm start |
Serve the production build (run build first) |
npm run lint |
ESLint across the project |
npm run data:download |
Download raw datasets into Data/raw/ |
npm run build:data |
Regenerate JLPT JSON in src/data/json/ and public/data/ |
application-face/
│
├── content/ # MDX content (read at build time, not bundled)
│ ├── blog/ # posts (frontmatter: title, date, tags, draft)
│ ├── changelog/ # one file per release (version, date, title)
│ ├── docs/ # API reference: data/ · engine/ · api/
│ └── learn/ # guides & cheat sheets
│
├── public/ # Static assets at /
│ ├── data/ # generated JLPT JSON (the open Data API)
│ ├── audio/ images/ fonts/
│
├── src/
│ ├── app/
│ │ ├── layout.tsx # Root: <html>, fonts, metadata, StoreHydrator
│ │ ├── globals.css # Tailwind v4 @theme + base styles
│ │ ├── sitemap.ts robots.ts # SEO routes
│ │ ├── not-found.tsx # 404
│ │ ├── (site)/ # Marketing route group — SiteHeader/SiteFooter
│ │ │ ├── page.tsx # landing (/)
│ │ │ ├── about/ contribute/
│ │ │ ├── docs/ # hub + [...slug] (+ layout with sidebar)
│ │ │ ├── learn/ blog/ changelog/
│ │ │ └── blog/rss.xml/ # RSS feed route
│ │ └── (game)/ # Game route group — keeps GameShell chrome
│ │ ├── review/ # spaced-repetition (FSRS) hub
│ │ ├── kana/ (+ play→/kana redirect)
│ │ └── kanji/ vocabulary/ listening/
│ │
│ ├── components/
│ │ ├── site/ # Marketing chrome + content UI (+ mdx/ renderer)
│ │ ├── game/ # Game-specific UI
│ │ ├── layout/GameShell.tsx # Shared game chrome
│ │ └── ui/ # Primitives: Button, Input, LevelPicker
│ │
│ ├── data/ # Pure data + typed loaders; srsItems (SRS units)
│ ├── engine/ # Framework-agnostic logic (quiz, kanaSession, srs/FSRS)
│ ├── hooks/ # React glue (useQuizSession, useKanaSession, useSrs)
│ ├── store/ # Zustand (settingsStore — JLPT level)
│ └── lib/
│ ├── cn.ts site.ts date.ts
│ ├── srs/ # IndexedDB (Dexie) store + JSON export/import
│ └── content/ # MDX content layer (blog, docs, learn, changelog)
│
├── docs/ # improvements.md (plan), map.md, copy specs
├── next.config.ts # Security headers + CSP, Turbopack root
└── tsconfig.json # strict; @/* → src/*
The route groups (site) and (game) do not affect URLs — they only let
the marketing pages and the game use different layouts/chrome. /, /kana,
/kanji, /vocabulary, /listening are unchanged.
data → engine → hooks → components. The engine imports no React or DOM;
components reach the engine only through hooks; data is the only place that
reads the JSON datasets. See content/docs/engine for the
contributor docs (also served at /docs/engine).
Blog posts, changelog entries, docs, and learn guides are MDX files under
content/. They're parsed with gray-matter and rendered server-side with
next-mdx-remote/rsc — so nothing is fetched at runtime and the strict CSP is
untouched. Add a post by dropping a .mdx file into content/blog/ (see
content/blog/_template.mdx); files starting with _ and draft: true are
excluded from listings, RSS, and the sitemap.
/review schedules practice with FSRS (via ts-fsrs). Card state lives in
IndexedDB (Dexie) on the device — no account, works offline, no CSP change.
Export backup / Import backup move progress between devices as JSON
(last-write-wins). Layers: src/engine/srs.ts (scheduler), src/data/srsItems.ts
(reviewable units), src/lib/srs/db.ts (store + backup), src/hooks/useSrs.ts
(reactive counts). See /docs/engine/srs for the full reference.
next.config.ts— security headers and a strict Content-Security-Policy (default-src 'self').NEXT_PUBLIC_ASSET_HOSTadds an allowed image origin (e.g. a Cloudflare R2/CDN domain) toimg-src.- Tailwind v4 — there is no
tailwind.config.ts. Theme tokens live insrc/app/globals.cssunder@theme. tsconfig.json— strict mode;@/*maps tosrc/*.- Fonts — self-hosted only. Do not add
next/font/google(offline + CSP constraint); self-host or name an installed font.
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_SITE_URL |
Recommended | Canonical origin for SEO, sitemap, and RSS (e.g. https://nihongo-master.app). Falls back to http://localhost:3000. |
NEXT_PUBLIC_ASSET_HOST |
Optional | Bare origin for externally hosted images (R2/CDN), added to the CSP img-src. |
Self-hosted (Coolify) behind Cloudflare; static images can be served from
Cloudflare R2 via NEXT_PUBLIC_ASSET_HOST. The app is fully static/SSG today —
no database is required at runtime (a REST API backed by Mongo/Redis is
planned; see /docs/api/overview).
npm run build
npm start # serves on PORT (default 3000)- Server Components by default; add
"use client"only at leaf nodes that need browser APIs or state. - Styling is Tailwind utilities; keep it monochrome (neutral scale + black/white).
- Commits follow Conventional Commits
(
feat:,fix:,docs:,chore:,refactor:…). - File naming — PascalCase components, camelCase hooks/utils, lowercase Next.js special files.
Code is released under the MIT License © Nihongo Master. The bundled
language data derives from third-party sources (KANJIDIC, JMdict, JLPT word
lists) under their own terms — see /docs/data/overview.