Skip to content
← All articles

Oct 04, 2026
2 min read

  • Offline-first
  • Next.js
  • PWA
  • EdTech
  • Architecture

In many Congolese schools the connection drops several times a day. Here is how I designed Masomo so attendance, grades and the cash desk never wait for the network.

When I started working on Masomo, a school-management platform for the DRC, the first real constraint wasn't a feature. It was the network.

A teacher takes attendance at 7:30. The secretary collects fees at the gate. The prefect prints report cards at the end of the period. If any of these actions shows a spinner because the connection dropped, people go back to paper — and they don't come back.

So the rule became simple: the network is an optimisation, not a requirement.

Treat the server as a sync target, not the source of truth for the UI

The classic web app reads from the API and renders. Offline-first flips it:

  1. The UI reads and writes a local store (IndexedDB in the browser).
  2. Every write is also appended to an outbox of pending operations.
  3. A background sync loop pushes the outbox when the connection comes back, then pulls what changed on the server.
type Op = {
  id: string;          // client-generated UUID → idempotent on the server
  entity: 'attendance' | 'payment' | 'grade';
  action: 'create' | 'update';
  payload: unknown;
  createdAt: number;   // device clock, for ordering only
};

The client-generated id matters more than it looks: if the sync request times out after the server saved it, the retry must not create a duplicate payment.

Design the data so conflicts are rare

Most "conflict resolution" problems are really data-modelling problems.

  • Append, don't overwrite. A payment is an immutable event. Corrections are new events (a refund, an adjustment), never an edit of the original.
  • Own the record. Attendance for a class on a given day belongs to that class's teacher. Two devices rarely edit the same thing.
  • Last-write-wins only where it's harmless, like a student's phone number.

With that, the sync loop stays boring — which is exactly what you want.

Money needs a frozen context

Schools in Congo charge in francs and dollars. If a parent pays in francs on Monday and the receipt is printed on Wednesday with Wednesday's rate, the numbers no longer match what the parent saw.

So each payment stores the rate used at the moment of payment, and the receipt is generated from that snapshot — online or not. (I wrote more about this in a separate article on handling two currencies.)

Show the sync state, honestly

People trust offline apps when they can see what is happening:

  • a small badge: synced, 3 pending, offline;
  • the time of the last successful sync;
  • pending items marked in lists, never hidden.

If users can't tell whether their work is saved, they will assume it isn't.

What I'd tell my past self

  • Start offline-first on day one. Retrofitting it onto an online app is a rewrite.
  • Make every write idempotent before writing a single sync line.
  • Test on a real cheap Android phone on a bad 3G connection, not on your laptop with DevTools throttling.

Offline-first is more work up front. But in a context where the network is unreliable, it is the difference between software people try and software people use.