Skip to main content

Command Palette

Search for a command to run...

Offline-First Is Not a Feature

Updated
7 min readView as Markdown
Offline-First Is Not a Feature
A
Independent Full-Stack Developer leveraging 5+ years of IT experience and CS background.

Most applications are built with one assumption:

The internet is available.

The UI loads data from an API. The user submits a form. The server saves it. The application moves on.

That works well when connectivity is reliable.

But what happens when it isn't?

A clinic loses its connection. A field worker has no signal. A power outage takes the network down. A small business is working from an area with inconsistent connectivity.

The user's work still needs to continue.

That is where offline-first architecture becomes important.

Offline-first starts before the first route or component

I don't think of offline-first as a feature that gets added after an application is already built.

It is an architectural decision.

The difference matters because offline-first changes how you think about:

  • Data storage

  • Application state

  • Reads and writes

  • Synchronization

  • Failed requests

  • Conflicts

  • Deletes

  • Network availability

If the application is designed around a remote API from the beginning, adding offline support later can become a significant architectural change.

With offline-first, the local data store becomes part of the application's normal operation.

The application should be able to do its core job whether the network is available or not.

Offline mode vs offline-first

These two ideas are often used interchangeably, but they are different.

Offline mode

Offline mode usually means the application has an online-first architecture with a fallback.

The application detects that the connection is gone and might:

  • Show cached data

  • Disable certain features

  • Display an offline message

  • Wait for the connection to return

The network is still the default assumption.

Offline-first

With offline-first, local storage is part of the normal architecture.

The application reads and writes locally first.

Synchronization with the server happens separately, usually in the background.

The basic flow becomes:

User → Local database → UI

and then:

Local database → Sync queue → Server

The important difference is that the user's core action does not have to wait for the network.

How I'm using this in Aafiyat

Aafiyat is a clinic management system I'm building for private practices.

One of the questions I asked while designing it was simple:

What happens if the clinic loses its internet connection in the middle of the working day?

A doctor shouldn't have to stop because the cloud is temporarily unavailable.

Aafiyat is built as an Electron application with SQLite as the local database.

Patient records, consultations, prescriptions and billing data are written locally first.

The application does not make a network request part of the critical save path.

The basic flow is:

  1. Generate a UUID for the record.

  2. Open the local database.

  3. Insert the record into SQLite.

  4. Notify the application that the local change happened.

  5. Add a synchronization job to the sync queue.

  6. Synchronize with Supabase when connectivity is available.

The important part is step three.

The user's data is saved before we worry about the network.

The UI can confirm the action immediately instead of waiting for an API response.

The sync queue

Saving locally is only half of the problem.

Eventually, the local data needs to reach the server.

That's where the synchronization queue comes in.

Aafiyat keeps pending synchronization jobs in a SQLite sync_queue.

A simplified flow looks like this:

User creates record
        ↓
SQLite
        ↓
sync_queue
        ↓
Internet available?
        ↓
   ┌────┴────┐
   │         │
  Yes        No
   │         │
Sync      Stay queued
   │
Supabase

If synchronization fails, the item stays in the queue and can be retried later.

This is one of the reasons offline-first applications require more planning than ordinary CRUD applications.

You aren't only building:

Create → API → Database

You're building something closer to:

Create
  ↓
Local storage
  ↓
Queue
  ↓
Retry
  ↓
Synchronize
  ↓
Resolve conflicts

The Nankana Home Care PWA

I'm using a similar idea in the Nankana Home Care project.

The platform includes an offline-first PWA for medical assistants who may need to record patient information without an internet connection.

The local data is stored using IndexedDB.

When connectivity becomes available, a synchronization worker processes the queued information and sends it to Supabase.

For example, patient data can be queued locally and synchronized later.

If a request fails, it isn't simply discarded.

The item remains queued so the application has another opportunity to synchronize it.

The architecture is different from Aafiyat because the technology is different, but the underlying idea is the same:

Local work first. Cloud synchronization second.

Synchronization is the hard part

This is where offline-first becomes much more interesting.

What happens when the same record changes in two places?

For example:

Local device
Patient name → "Muhammad Ali"

        ↓

Server
Patient name → "Muhammad A."

Which version should win?

There isn't one universal answer.

Possible strategies include:

  • Last-write-wins

  • Server-wins

  • Client-wins

  • Manual merging

  • Domain-specific conflict resolution

For Aafiyat, I'm using a last-write-wins approach based on timestamps.

The most recent updated_at value determines which version is accepted.

That's a deliberate trade-off based on the workflow I'm building for.

The important thing isn't that one strategy is always correct.

The important thing is to decide what should happen before conflicts occur in production.

Deletes are also harder

Updates aren't the only problem.

Consider a record deleted while the device is offline.

The application needs to remember that deletion and eventually communicate it to the server.

Otherwise, synchronization could accidentally recreate the record.

Offline-first means thinking about the entire lifecycle of data:

Create
Read
Update
Delete
Sync
Retry
Conflict

Not just the happy path.

Do you actually need offline-first?

No.

Not every application needs this architecture.

A simple marketing website probably doesn't need a local database and synchronization queue.

A basic landing page doesn't need to continue operating without connectivity.

But the situation changes when the user's core job depends on the application.

Think about:

  • Clinics

  • Pharmacies

  • Field workers

  • POS systems

  • Local businesses

  • Applications used in areas with unreliable connectivity

In these environments, connectivity can become an operational constraint rather than a minor inconvenience.

That's when offline-first starts making sense.

The trade-off

Offline-first isn't free.

It adds complexity.

You now have to think about:

  • Local storage

  • Two sources of data

  • Synchronization

  • Retry mechanisms

  • Conflicts

  • Storage limits

  • Deletes

  • Background processing

The initial architecture takes longer.

But the benefit is that the application doesn't have to stop doing its core job every time the network disappears.

For the right environment, that trade-off is worth considering.

Build for the environment your users actually have

This is probably the biggest lesson I've taken from building these systems.

It's easy to design software around an ideal environment:

Fast internet. Modern hardware. Stable power. Constant connectivity.

Real users don't always have that environment.

In the applications I'm building, I want the architecture to reflect that reality.

The internet is useful.

The cloud is useful.

But neither should automatically become a requirement for every core action.

Offline-first isn't a feature you add at the end.

It's a decision you make when you design the application.

And sometimes, that decision determines whether the software keeps working when everything around it doesn't.