Portfolio · 2026Harare → Web
Hello.
Mhoroi.
Bonjour.
Hola.
Konnichiwa.
Welcome.
ShaunChivandire
00%
Loading...
Loading…

Menu

Connect

Back to Blog
EngineeringDecember 15, 20248 min read

Shaun Chivandire

Shaun Chivandire

Founder & Developer

Server room with network cables representing offline-first architecture

The Problem Nobody Talks About

When I first started building apps for Zimbabwe, I made a critical mistake: I assumed connectivity. My first app required constant internet. It failed.

In emerging markets, network conditions are unpredictable. Users switch between 2G, 3G, and WiFi constantly. Data is expensive—$5 per GB in Zimbabwe. And yet, most tutorials assume you're building for San Francisco.

What Offline-First Actually Means

Offline-first isn't about caching. It's a fundamental shift in how you think about data:

  • Local-first: All data lives on the device first
  • Sync when possible: Background sync when network appears
  • Conflict resolution: Handle what happens when two devices edit the same thing
  • Progressive enhancement: Add online features as connectivity allows

The best offline app is one where users don't know they're offline.

The Architecture Pattern I Use

Here's the stack I've standardized on for every project:

1. IndexedDB for Local Storage

SQLite alternatives exist, but IndexedDB works in browsers and PWAs without additional dependencies:

// Using Dexie.js for IndexedDB
const db = new Dexie('FareWise');
db.version(1).stores({
  trips: '++id, route, platform, fare, synced',
  settings: 'key, value'
});

2. Service Workers for Caching

Every asset should be available offline:

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/',
        '/styles.css',
        '/app.js',
        '/icons/icon-192.png'
      ]);
    })
  );
});

3. Background Sync for Data

When the network returns, push pending changes:

async function syncPendingTrips() {
  const unsyncedTrips = await db.trips
    .where('synced')
    .equals(false)
    .toArray();

  for (const trip of unsyncedTrips) {
    try {
      await api.createTrip(trip);
      await db.trips.update(trip.id, { synced: true });
    } catch (err) {
      // Will retry on next sync
      console.log('Sync failed, will retry');
    }
  }
}

Real Results

This pattern powers FareWise, which works 100% offline. Drivers calculate fares with zero connectivity, and data syncs when they're back in range.

The key metrics:

  • 0 bytes required for core functionality
  • < 50KB initial bundle (compressed)
  • Sub-second load on 2G connections

The Takeaway

If you're building for emerging markets, start offline-first. Don't retrofit it later. The architecture decisions you make on day one determine whether your app works in the real world.

The Newsletter

Weekly insights on building products, AI agents, and tech for emerging markets. No fluff, just actionable ideas.

Join 500+ buildersWeekly readers

Get the next issue

Free · Every week

No spam, ever. Unsubscribe anytime with one click.