Case study · Web app

JLPT Étude

JLPT N1の漢字・語彙・文法を、毎日自動で作られる短いレッスンで学ぶWebアプリです。試験日と勉強できる日数から、その日にやる量と時間を計算します。
A web app to prepare the JLPT N1 (kanji, vocabulary, grammar) with a short lesson built automatically every day. It works out how much to do, and how long it will take, from the exam date and the days you can study.

アプリを開く / Open the app

1. 全体の構成 Architecture

サーバーを自分で管理しない構成です。画面とAPIはAzure Static Web Apps、データはBlob Storage。学習データはまずブラウザに保存されるので、APIが止まっても勉強は続けられます。
No servers to manage: Azure Static Web Apps serves the app and its API, Blob Storage keeps the data. Progress is saved in the browser first, so studying continues even if the API is down.

JLPT Étude architecture: browser app (offline first) talking to Azure Static Web Apps (static site and Azure Functions API), Azure Blob Storage with 30-day versions, Application Insights, Google sign-in, and a GitHub Actions pipeline with tests, deploy, verification and a 30-minute health check ブラウザ / Browser HTML · CSS · JavaScript (no framework) 毎日のレッスン / Daily lesson planning · spaced repetition 学習データ / Progress localStorage (offline first) 同期とマージ / Sync & merge ETag · conflict merge FR / EN · 4 637 items N1 Microsoft Azure (East Asia) Azure Static Web Apps static site + Azure Functions API (Node 20) /health · /auth/login · /me · /progress/N1 Azure Blob Storage (private) 1 file per user · versions + soft delete 30 days Application Insights API errors and calls · capped at 0.1 GB/day HTTPS · save / restore Google Sign-In ID token → API session (no password) GitHub Actions ① Test テスト · 62 tests · 15 s ② Deploy デプロイ · version stamp ③ Verify 確認 · /api/health PR → preview URL · manual run → rollback to any commit every 30 min: health check → e-mail alert if it fails

2. 毎日のレッスン The daily lesson

試験日・週に勉強できる日数・1日の上限時間から、毎日の新しい語・漢字・文法の数を計算します。実際の勉強ペースが予定より遅いと、アプリが知らせて計画を合わせます。
From the exam date, the study days per week and a daily time limit, the app computes how many new words, kanji and grammar points to learn each day. If the real pace is slower than planned, it says so and adapts the plan.

  1. 復習 Review 忘れる前に復習(間隔反復)。正解するほど間隔が長くなります。 Spaced repetition: the better you know an item, the later it comes back.
  2. 新出 Learn 5つずつ(文法は2つずつ)カードで覚えて、すぐに確認問題。 New items in batches of 5 (2 for grammar), each checked right after its card.
  3. 小テスト Test JLPT形式の問題(漢字読み、文法形式、文の組み立て★)。半分は苦手な項目から。 JLPT-style questions (kanji reading, grammar, ★ sentence order), half picked from weak items.
  4. やり直し Retry 間違えた問題はレッスンの最後にもう一度(最大3回)。 Missed questions come back at the end of the lesson (up to 3 times).

3. 本番運用 Running it in production

よく聞かれる質問に、できていることと、できていないことの両方で答えます。2026年10月11日に、下の手順はすべて一度実行して確認しました。
Common questions, answered with what is in place and what is not. Every procedure below was run at least once on October 11, 2026.

ポッドやワーカーは? Pods or workers in production?

ありません。Static Web AppsとAzure Functionsはマネージドサービスで、スケールもAzureが行います。アクセスが少ないアプリには、コストもほぼ0円で、パッチを当てるサーバーもありません。無料プランの上限(月100GBの転送量)を超えたら、App Serviceやコンテナへの移行を検討します。
None. Static Web Apps and Azure Functions are managed services that scale on their own. For a low-traffic app this costs close to nothing and leaves no server to patch. Past the free plan limits (100 GB of bandwidth per month), I would move to App Service or containers.

デプロイとロールバックは? Deployments and rollbacks?

mainにプッシュすると、62のテスト → デプロイ → 確認が自動で走ります。テストが1つでも失敗するとデプロイしません。確認では /api/health が今デプロイしたコミットを返すまで待ちます。プルリクエストごとにプレビュー用のURLが作られ、閉じると消えます。ロールバックは任意のコミットを再デプロイするだけで、実測2分3秒。最初の試行では短いコミットIDを受け付けない不具合が見つかり、その場で直しました。
A push to main runs 62 tests → deploy → verification. A single failing test blocks the deploy. Verification waits until /api/health returns the commit that was just deployed. Every pull request gets its own preview URL, removed when it is closed. A rollback redeploys any earlier commit: measured at 2 min 03 s. The first attempt failed because short commit IDs were not accepted; I fixed it on the spot.

障害時の予備環境は? Fallback if it crashes?

一番の守りはオフラインファーストです。APIが止まっても、学習データはブラウザに残り、復旧後に自動で同期されます。サーバーのデータは保存のたびにバージョンが残り、削除しても30日間は戻せます。復元スクリプトで、上書きされたデータの復元も試しました。まだないもの:別リージョンの待機環境。いまの利用規模では必要ないと判断しています。
The main safety net is offline first: if the API is down, progress stays in the browser and syncs automatically once it is back. On the server, every save keeps a version and deletions can be undone for 30 days; I tested restoring an overwritten file with the restore script. Not in place: a standby environment in another region, not needed at this scale.

アーキテクチャは? Architecture?

上の図のとおりです。アドレスは jlpt.simondepussay.com(CloudflareのDNSからAzureへのCNAME、証明書はAzureが自動更新)。APIは4つのルートだけ。同じアカウントを2台で使っても、ETagで競合を検出し、決めたルールでマージするので、データは失われません。
See the diagram above. The address is jlpt.simondepussay.com (a CNAME from Cloudflare DNS to Azure, with a certificate renewed automatically by Azure). The API has only four routes. When the same account is used on two devices, ETags detect the conflict and the data is merged with defined rules, so nothing is lost.

ビジネスルールは実際にいくつ実装・テストされている? How many business rules are really implemented and tested?

38のビジネスルール、10のデータチェック、14のAPIテストで、合計62。全部で約1秒、CIでは約15秒で終わります。下の表を参照してください。
38 business rules, 10 data checks and 14 API tests: 62 in total. They run in about 1 second locally and about 15 seconds in CI. See the table below.

24時間の保守は? 24/7 maintenance?

個人プロジェクトなので、人によるオンコールはありません。そのかわり、30分ごとの死活監視(失敗するとメール)、デプロイ後の自動確認、Application Insightsでのエラー記録、2分のロールバック、30日分のバックアップ、そしてオフラインで動くアプリで、止まっても学習が続くようにしています。対応までの時間は、メールを読むまでの時間です。
It is a personal project, so there is no human on call. Instead: a health check every 30 minutes (e-mail on failure), automatic verification after each deploy, API errors logged in Application Insights, a 2-minute rollback, 30 days of backups, and an app that keeps working offline. Response time is the time it takes me to read the e-mail.

4. テスト Tests

分類 Area数 Count例 Examples
間隔反復 Spaced repetition9intervals 1/2/4 days then × ease, ease never below 1.3, cap at 365 days, levels
計画 Planning9new items per day = remaining ÷ study days, final review weeks, time cap, double day
実際のペース Real pace7no measure before 14 days, warning when below plan, streak, measured lesson time
同期 Sync3merge keeps the most-reviewed item, days combined, newest settings win
レッスン Lesson7order of steps, batches of 5, retry max 3 times, free practice leaves the plan untouched
問題・言語 Exercises & language34 distinct choices, never another valid reading, FR/EN everywhere except Japanese
データ Data10every word and kanji translated, every quiz sentence translated, 4 distinct options
API14401 without session, 412 on conflict, 428 without If-Match, users isolated, account deletion

テストしていないもの:画面の見た目とブラウザでの通しテスト(E2E)。これは手作業で確認しています。
Not covered: visual rendering and end-to-end browser tests, which I still check by hand.

5. 設計で考えたこと Design decisions

6. 作り方と学んだこと How it was built, what I learned

Claude Code(AIのコーディングエージェント)と一緒に作りました。何を作るか、構成、ルール、優先順位を決め、毎回動作を確認して、使いながら直してきたのは私です。AIで速く作れるからこそ、テスト、監視、ロールバックのように「動いていると証明できる」部分を後回しにしないことが大切だと学びました。
Built with Claude Code, an AI coding agent. I decided what to build, the architecture, the rules and the priorities, checked every change and kept fixing things while using the app every day. Building fast with AI taught me not to postpone the parts that prove it works: tests, monitoring and rollback.