
Eduteck
A course platform where students lose their place every time the train goes into a tunnel is a course platform students stop using.
The problem
An ed-tech company selling online course subscriptions to students, primarily on mobile.
Students watch lessons on mobile data, in transit, on cheap devices. The failure mode isn't a crash, it's friction: a page that reloads to show a progress bar, a video that forgets where you were, a dashboard that takes three seconds to paint. Each one is a small reason to close the tab, and course completion is the metric the whole business runs on.
Build the student-facing web application against an existing REST API: authentication, course catalogue, the lesson player, and progress tracking — responsive first, because most of the traffic is phones.
- The API contract was fixed — the backend already existed and wasn't changing for me.
- Mobile-first, on real network conditions rather than a fast dev machine.
- No design system to inherit; the UI had to be built and kept consistent from scratch.
What I built
React single-page app covering sign-in, catalogue, course detail, and the lesson player.
Per-lesson completion state that updates as a student watches, without a page reload, and survives a dropped connection.
Tailwind-based component set giving the app one consistent look from small phones through desktop.
How it's built
A React SPA talking to an existing REST backend over token auth. The interesting part is client-side: progress is written to local state first and synced to the API in the background, so the UI never blocks on the network and a dropped connection doesn't lose a student's place.
Decisions & trade-offs
Write progress locally first, sync in the background
The straightforward build POSTs progress to the API and waits. On a good connection that's invisible; on mobile data it means the progress bar lags the video by seconds, or a dropped request silently loses the update.
- ·Write through to the API and reflect the response
- ▸Update local state immediately, sync to the API in the background
chose Local first, background sync — The student's own device is a perfectly good source of truth for 'where am I in this video'. Treating the API as a sync target rather than a gatekeeper made the UI instant and made a dropped connection a non-event.
Two sources of truth, which means reconciliation. A student who watches on their phone and then opens a laptop can see the two disagree until the next sync. For a single-device-at-a-time product that was an acceptable trade; for a product where people genuinely switch devices mid-lesson it would not be.
Tailwind instead of a component library
No existing design system, and a UI that had to look deliberate across phone, tablet, and desktop without a designer on call.
- ·A component library (MUI / Ant) for speed
- ▸Tailwind and build the handful of components the app actually needs
chose Tailwind with a small hand-built component set — The app needed about eight repeated components. A library would have shipped a lot of CSS and JS for the other ninety, and fighting its defaults to match the brand usually costs more than writing the eight.
No accessibility behaviour for free. Focus management and keyboard handling on the interactive pieces were mine to get right, and that's slower than inheriting it from a mature library.
Hard problems
Progress that updates during playback without re-rendering the player
- symptom
- Progress needs to update continuously as a lesson plays. Naively wiring playback time into React state re-renders the tree several times a second, and on low-end phones that shows up as dropped frames in the video itself.
- diagnosis
- The stutter only appeared on throttled CPU, which is exactly the device profile most of the traffic was on — so it wasn't visible during normal development.
- fix
- Decoupled the high-frequency playback signal from React's render cycle: playback position is tracked outside component state and only promoted to state at the thresholds that actually change what's on screen (completion crossings, not every tick). Renders dropped to a handful per lesson.
Outcomes
Playback position is held client-side and synced when the network returns.
Every figure is tagged with how it was arrived at. Nothing here is an estimate dressed up as a measurement.