Offline-First PWA Architecture in JG Mart: Zero Cloud Lock-In
How we architected robust client-side persistence and optimistic UI updates for hyperlocal grocery delivery in Japan Garden City — a 1,700-family residential complex (the pilot target, not a proven usage count) across 27 buildings with zero-dependency vanilla JavaScript.
The Problem: Delivery Apps Break When Networks Collapse
In Dhaka, network reliability is a feature, not a given. 4G drops between buildings, WiFi dead zones in stairwells, and bandwidth throttling during peak hours are daily realities. A grocery delivery PWA that requires constant connectivity isn't just annoying — it's unusable.
When we designed JG Mart for Japan Garden City, the constraint was absolute: every feature must work offline, sync when connectivity returns, and never lose an order in transit. No React, no build step, no framework — just vanilla JavaScript, service workers, and a custom persistence layer.
Architecture: The Three-Layer Sync Stack
We built a three-layer architecture that separates concerns cleanly:
- Layer 1: Local-First State — All cart, order, and user data lives in IndexedDB via a thin abstraction layer. The UI reads from this local store instantly, never waiting for network.
- Layer 2: Background Sync — A service worker queues mutations when offline and replays them when connectivity returns, using exponential backoff and deduplication.
- Layer 3: Supabase Realtime — When online, the app subscribes to Supabase Realtime channels for live order status updates, falling back to polling if the WebSocket drops.
// Simplified JG Mart persistence layer
class JGDataStore {
constructor() {
this.db = new Map(); // In-memory index
this.queue = []; // Mutation queue for sync
this.online = navigator.onLine;
}
async get(key) {
// 1. Check memory
if (this.db.has(key)) return this.db.get(key);
// 2. Check IndexedDB
const record = await this.idb.get(key);
if (record) this.db.set(key, record);
return record;
}
async put(key, value) {
// Optimistic local write
this.db.set(key, value);
await this.idb.put(key, value);
if (this.online) {
await this.syncToSupabase(key, value);
} else {
this.queue.push({ key, value, ts: Date.now() });
}
}
}
WhatsApp-Native Ordering: Why We Chose Familiar UX
Instead of building a custom checkout flow, we integrated WhatsApp deep-linking. Users browse the catalog, add items to cart, and on checkout — the app generates a formatted WhatsApp message with their order details and opens it directly.
Why This Works for Emerging Markets
WhatsApp penetration in Bangladesh exceeds 90% among smartphone users. By using WhatsApp as the ordering channel, we eliminated the need for user accounts, payment processing, and order confirmation pages. The user's WhatsApp chat history is the order record.
Performance: Zero Dependencies, 82 Products, Sub-3s Load
JG Mart ships as a single HTML file with inline CSS and JavaScript. No build step, no bundler, no framework. Yet it handles:
- 82 products with variant support (size, weight, bundle)
- Cart persistence across sessions
- Order history with status tracking
- Bilingual UI (English + Bengali)
- Operator dispatch dashboard
"The best framework is no framework. JG Mart proves that vanilla JS, when architected well, can outperform bundled alternatives for constrained use cases."
Key Takeaways & Lessons Learned
- Optimistic UI is non-negotiable — Users expect instant feedback. Write to local state first, sync later. Never block the UI on network round-trips.
- Service workers are tricky — Cache strategies must be explicit. We use stale-while-revalidate for catalog data and network-first for order mutations.
- WhatsApp is a platform — Treat it as your backend. Order confirmation, customer support, and delivery tracking all flow through the same chat thread users already trust.