Skip to content
theakrista.dev
Case Studies

2026 - Present

Live, visit site

nihongojouzu.jp

A production JLPT study PWA with spaced repetition, push notifications, and a custom furigana engine, built solo, end to end.

Founder & sole engineer

Next.jsReactTypeScriptZustandSupabasePWA / Service WorkersWeb PushGA4Microsoft Clarity
monthly infra cost, the .jp domain is the only bill
¥0
spaced-repetition scheduler tuned for JLPT study
SRS
furigana rendering engine for ruby text
Custom
magic links + Google sign-in, no passwords stored
Passwordless

The Problem

Studying for the JLPT N2 as a working engineer in Japan, I hit the same wall every learner does: flashcard apps treat Japanese like a list of English words. They can't render furigana properly, their review scheduling ignores how memory actually decays, and they don't survive a subway tunnel.

I wanted a study tool that behaves like a real product: reviews scheduled by a spaced-repetition algorithm, reminders that arrive as push notifications at the right moment, and typography that renders ruby text the way a Japanese textbook does.

I also wanted proof-of-craft: a system I own from database schema to service worker, running in production, with real users and real analytics driving iteration.

The Constraints

A budget of exactly ¥0

Built alongside a full-time senior engineering role, with a hard rule: no recurring infrastructure bills. Hosting, database, auth, push notifications, and analytics all had to fit on free tiers, and every architectural choice had to minimize operational load too.

Japanese typography is unforgiving

Furigana (ruby annotations above kanji) must align per-character across mixed kanji/kana text, at any font size, on any device. Off-the-shelf ruby rendering breaks on compound words and long readings.

Offline is the normal case

Study happens on trains. The app had to be installable and functional through connectivity gaps, which shapes state management, caching, and sync design from day one.

Auth without friction

Nobody wants a password for a study app. Sign-in is passwordless only (a magic link or Google) which means email deliverability, token expiry, OAuth redirects, and session handling all have to be right.

The Architecture

A serverless-first PWA assembled entirely from free-tier infrastructure: Next.js on the edge for the shell, Supabase for auth/data, the browser's own Web Push for notifications, and a client that treats the network as an enhancement. Review state lives in Zustand with persistent storage, so a study session never blocks on a request.

Architecture diagram of nihongojouzu.jp: Next.js PWA client with Zustand store, furigana engine and service worker, connected to Supabase auth and Postgres, Web Push notifications, and GA4 plus Clarity analytics, all on free-tier infrastructure
  • The spaced-repetition scheduler computes the next review interval from answer quality and history, study sessions read and write locally first, then sync, so a tunnel never interrupts a review.
  • The furigana engine tokenizes kanji/kana boundaries and emits per-segment ruby markup, solving alignment cases (compound words, long readings, mixed script) that naive <ruby> tagging gets wrong.
  • Web Push review reminders run through the service worker, the app nudges you when reviews come due, which is the mechanic that makes spaced repetition actually work.

The Product

nihongojouzu.jp home screen: streak, daily goal, study booths and the Churu-kun mascot
Home, streaks, daily goals & study booths
nihongojouzu.jp kanji study screen
Kanji study with custom furigana rendering
nihongojouzu.jp JLPT mock test screen
Full JLPT N2 mock tests

The Trade-offs

Every architecture is a set of bets. These are the ones I made, and what I gave up to make them.

Backend

Supabase (managed Postgres + auth)overA custom Node.js API I'd fully control

I build APIs for a living; that's exactly why I didn't build one here. A solo side project's scarcest resource is maintenance attention. Supabase's auth, row-level security, and Postgres bought me a production backend for near-zero ops.

State management

Zustand with local persistenceoverServer-state libraries (React Query) as source of truth

For offline-first study flows, the client is the source of truth during a session. A small persisted store with explicit sync beats cache-invalidation gymnastics when the network is optional.

Furigana

Writing a custom rendering engineoverExisting ruby-text libraries

I tried the libraries first. They mis-segment compound kanji and misalign readings, unacceptable in a product whose entire job is teaching correct readings. This was the one place where 'build' beat 'buy' on quality grounds.

Authentication

Passwordless only, magic links + Google sign-inoverPassword-based accounts

No stored passwords shrinks the security surface to nothing worth stealing. Google sign-in gives the one-tap path most users pick; magic links cover everyone else, and both flows share the same Supabase session handling.

Notifications

Web Push via the service workeroverPaid notification services

Web Push is a free, standard browser capability, no per-message pricing, no vendor SDK. It costs more engineering up front (subscription lifecycle, iOS install requirement), but it keeps the entire reminder system at ¥0/month forever.

Cold starts

Free-tier hosting kept warm by an uptime monitoroverPaying for an always-on instance

Free-tier servers spin down when idle, so the first visitor eats a long cold start. An UptimeRobot health-check ping every 5 minutes keeps the instance warm, and doubles as free uptime alerting. The ¥0 bill stays ¥0, and no user ever waits for a cold boot.

AI in the workflow

Claude at authoring time, on a flat-rate subscriptionoverRuntime LLM API calls with a metered key

Study content and code are generated and refined with Claude during development, then reviewed before anything ships. Production carries no LLM API key at all, nothing metered per request, and nothing to leak. I've seen what a leaked key costs in production; the cheapest key to secure is the one that doesn't exist.

The Outcome

nihongojouzu.jp is live, installable, and in active use, a complete production system owned end to end by one engineer.

  • Shipped a full spaced-repetition study platform: scheduler, decks, review flows, streaks, and push-notification reminders.
  • Shared free with the JLPT N2 community, no ads, no paywall. The ¥0 infrastructure is what makes giving it away sustainable.
  • The entire production stack (hosting, database, auth, Web Push notifications, and analytics) runs on free tiers: ¥0/month in infrastructure, with the nihongojouzu.jp domain as the only paid line item.
  • The custom furigana engine renders textbook-quality ruby text across devices, the feature users notice first.
  • Instrumented with GA4 and Microsoft Clarity; session replays directly shaped the review-flow UX and onboarding.
  • Serves as a living reference of my end-to-end range: product thinking, schema design, PWA engineering, auth, and analytics-driven iteration.

Lessons Learned

Offline-first changes everything upstream

You can't bolt offline onto an online app. Making the client the session source of truth dictated the state library, the sync model, and even the auth flow. Decide your connectivity story before your architecture.

Buy the platform, build the differentiator

Supabase for the commodity layers, custom code for the furigana engine, the inverse of what ego suggests and exactly what the product needed. Build only where you can be meaningfully better.

Analytics beats intuition, even at tiny scale

Clarity replays showed users hesitating exactly where I'd assumed the UX was obvious. Ten real sessions taught me more than a hundred self-tests. Instrument from day one, even with no users yet.