Case Study

Verbalize

3,000+ words LCP p50 ≈ 1.17s (Fast 4G + 4× CPU) TTFB 28–69ms

The vocabulary tools I used for GRE and IELTS prep felt like digital chores. Long lists you were meant to power through and somehow remember. That’s not how these words show up in real life. You meet them in short bursts, in context, usually while doing something else.

Verbalize tries to match that. Instead of a giant deck, it gives you a steady trickle of exam‑grade words, lets you mark the ones that matter, and follows you into places you already visit, like a new browser tab.

Under the hood it’s a small system: a browser UI, an API running at the edge, and a simple extension surface. The interesting part was never the stack. It was getting three pieces of code to agree on what “a word” is.


One Definition of a Word, Everywhere

The first thing I did was refuse to let each part of the system invent its own concept of a word. Both the API and the clients import a shared schema:

type Dataset = "gre" | "ielts";

type Word = {
  id: string;
  term: string;
  definition: string;
  examples: string[];
  dataset: Dataset;
};

This type lives in a small shared package. The API uses it to validate and return data; the React app and the extension use it to drive UI. When I added a difficulty tag or an extra example later, it meant updating the shared type once and following the type errors to the places that cared.

There’s one “give me a word” procedure in the system, not three slightly different versions spread across the codebase. That alone saved me from a class of bugs I’ve shipped before.


One Word at a Time

The main experience is a discovery view. You pick an exam track, GRE or IELTS, and the app surfaces one word with its definition, example sentence, and a few extra details. Save it, mark it as important, or move on.

I wanted this to feel like a daily routine rather than a dashboard. The app remembers your chosen dataset locally, so you’re not re‑selecting it every visit. A small bit of logic decides what to show next: a deep‑linked word if you followed one in, a search result if you searched, otherwise a random word from your pool.

The client leans on a typed API client and data‑fetching hooks instead of manual fetch calls:

function useRandomWord(dataset: Dataset) {
  return useQuery({
    queryKey: ["randomWord", dataset],
    queryFn: () => api.words.random({ dataset }),
  });
}

The real implementation handles error states, retries, and stale‑while‑revalidate, but the shape is the same. The UI asks for “a random word from this dataset” and never learns about URLs or JSON structures.

Pronunciation lives in a small custom hook that owns audio playback state, so the card component can stick to layout and interaction.


What Marking a Word Actually Does

I didn’t want “important” to be a star icon you tap and forget. It had to be a signal the system could use.

Marking a word as important updates two things: your own status for that word, and a global count that powers a simple “popular words” view. Both updates are optimistic in the UI and confirmed by the API afterward. Other parts of the app stay in sync by listening for a small event when a word’s status changes, rather than hauling in a heavy global store. The backend remains the source of truth, and the front‑end doesn’t feel laggy.

The side effect is a gentle feedback loop. Words you and others care about become easier to revisit, without anyone being forced into study plans or flashcard regimes.


Staying Out of the Way

Visually, Verbalize tries to get out of the way. It’s built from a small design system: tokens for colors and typography, a few reusable components for cards, buttons, and layout, and micro‑animations so state changes are clear without being noisy.

The accessibility work is practical rather than dramatic. Skip links for keyboard users, aria-labels on icon‑only buttons, layouts that adapt between desktop and mobile without breaking reading flow. None of it is glamorous. All of it keeps the app from feeling fragile as it grows.


The New Tab Extension

Once the main app felt solid, I built a browser extension that turns the new tab page into a lightweight Verbalize surface.

The extension reuses the same word model, typed API client, and UI pieces as the main app. It shows a single word from your chosen dataset, with the same card and actions, in a simplified layout that respects new‑tab constraints. Because the shared types and components already existed, the extension was mostly wiring and packaging. No new concepts, just the same experience in a place you see many times a day, which quietly helps with spaced repetition.

Verbalize isn’t trying to be the definitive exam prep platform. It shows you one useful word at a time, in places you already are, and lets you mark the ones that matter. That felt like the right distance between “serious studying” and “one more tab you don’t dread opening.”