Field workers — technicians, delivery drivers, warehouse staff in remote locations, sales reps visiting customers — have always been the last mile of ERP adoption. They need to log work orders, record deliveries, check inventory, and submit forms from the field. But Indonesian field workers frequently operate in areas with poor or intermittent connectivity: rural areas, warehouse basements, factory floors with WiFi dead zones. Building a native mobile app is expensive and requires separate development for iOS and Android. Progressive Web Apps (PWAs) solve this elegantly: one codebase, installable on any device, with offline capability built on service workers and IndexedDB.
For internal field worker tools, PWAs offer the right balance of capability and cost. A PWA is a web application that behaves like a native app: it can be added to the home screen, receives push notifications, works offline, and runs full-screen without a browser chrome. Development and maintenance costs a fraction of native app development because it's the same codebase as the web ERP. No app store approval process. Updates deploy instantly — the next time a user opens the app, they get the latest version without a manual update step. For Indonesian businesses that can't staff or budget a separate mobile development team, PWA is the practical answer.
Offline capability in a PWA is built on two browser APIs. Service Workers: a JavaScript file that runs independently of the page, intercepts network requests, and serves cached responses when the network is unavailable. The service worker caches the app shell (HTML, CSS, JS) so the app loads instantly even with no connection. IndexedDB: a client-side database that stores structured data in the browser. Field workers can read records (customer data, work order details, inventory levels) and write new records (completion forms, delivery confirmations, damage reports) even when offline. When connectivity returns, the service worker syncs the IndexedDB data to the server.
Offline data sync creates conflicts when the same record is modified both offline and on the server before sync. Your sync strategy must define how conflicts are resolved. For most field ERP use cases, a 'last write wins' strategy is acceptable for low-stakes data (a GPS location update, a time log entry). For transactional data (a delivery confirmation, a payment receipt), the sync should queue operations in order and apply them sequentially — conflicts are prevented by not allowing two users to modify the same record in the offline state. Design your data model to minimize shared mutable state in field workflows.
PWA Field ERP Architecture — Offline-First
ONLINE MODE: OFFLINE MODE:
┌──────────────────────────┐ ┌──────────────────────────┐
│ Field Worker Device │ │ Field Worker Device │
│ │ │ │
│ PWA App Shell (cached) │ │ PWA App Shell (from cache)│
│ │ │ │ │ │
│ Service Worker │ │ Service Worker │
│ ├─ fetch interceptor │ │ ├─ serves from cache │
│ ├─ cache strategy │ │ ├─ queues writes │
│ └─ sync queue │ │ └─ Background Sync API │
│ │ │ │ │ │
│ IndexedDB (local) ◄────┐│ │ IndexedDB (local) │
│ ├─ today's work orders ││ │ ├─ reads: all available │
│ ├─ customer data ││ │ └─ writes: queued sync │
│ └─ pending sync queue ││ │ │
└───────────┬──────────────┘│ └──────────┬────────────────┘
│ (sync) │ │ (reconnect)
▼ │ ▼
┌──────────────────────────┐│ ┌──────────────────────────┐
│ ERP Backend (NestJS) ││ │ Background Sync fires │
│ POST /api/work-orders ││ │ Service Worker processes │
│ GET /api/sync/today ││ │ queued operations in order│
│ PATCH /api/deliveries │└────────│ Conflict resolution runs │
└──────────────────────────┘ └──────────────────────────┘
Offline data set per field worker (downloaded each morning):
• Assigned work orders for today
• Customer address + contact data for those work orders
• Product/service catalog for their service category
• Reference data (fault codes, resolution types)
Size estimate: ~500KB per worker — fast sync on any connectionFrom my experience implementing ERPs at Commsult: design the offline data set to be as small as possible. A field technician needs the work orders assigned to them today, the customer address and contact info, and the product/service catalog for their domain. They do not need the entire customer database or all historical invoices. Limit the offline data set to what's needed for that day's work, downloaded at the start of the shift. This keeps the IndexedDB manageable and reduces sync complexity significantly.
A PWA requires three things to be installable: a Web App Manifest (a JSON file that defines the app name, icon, display mode, and start URL), a registered Service Worker, and HTTPS (required for service workers to function). The manifest controls how the app appears when installed — choose 'standalone' display mode to run without browser chrome, set the theme color to match your ERP brand, and include icons in multiple sizes. On Android, the browser will automatically prompt 'Add to Home Screen' when these requirements are met. On iOS, users must manually use the Share menu. Make sure your onboarding flow explains this iOS-specific install step.
// service-worker.ts — ERP Field PWA
const CACHE_VERSION = 'erp-field-v1';
const APP_SHELL = ['/offline.html', '/manifest.webmanifest'];
// Cache app shell on install
self.addEventListener('install', (event: ExtendableEvent) => {
event.waitUntil(
caches.open(CACHE_VERSION).then(cache => cache.addAll(APP_SHELL))
);
});
// Network-first for API calls; cache-first for static assets
self.addEventListener('fetch', (event: FetchEvent) => {
const { request } = event;
if (request.url.includes('/api/')) {
// API: try network, fallback to IndexedDB data
event.respondWith(
fetch(request).catch(() => {
// Network unavailable — queue the write for later
if (request.method !== 'GET') {
queueOfflineAction(request.clone());
return new Response(
JSON.stringify({ queued: true, message: 'Saved offline, will sync when connected' }),
{ headers: { 'Content-Type': 'application/json' } }
);
}
// For GETs, try cached data
return caches.match(request) ?? Response.error();
})
);
} else {
// Static assets: cache-first
event.respondWith(
caches.match(request).then(cached => cached ?? fetch(request))
);
}
});
// Background sync — fires when connection is restored
self.addEventListener('sync', (event: SyncEvent) => {
if (event.tag === 'erp-sync-queue') {
event.waitUntil(processOfflineQueue());
}
});
async function processOfflineQueue() {
const db = await openIndexedDB('erp-offline', 1);
const queue = await db.getAll('sync-queue');
for (const item of queue) {
try {
await fetch(item.url, { method: item.method, body: item.body });
await db.delete('sync-queue', item.id);
} catch {
break; // Still offline — stop processing, retry next sync
}
}
}
// Web App Manifest (public/manifest.webmanifest)
const manifest = {
name: "Commsult Field ERP",
short_name: "Field ERP",
display: "standalone",
background_color: "#0f172a",
theme_color: "#3b82f6",
start_url: "/field",
icons: [
{ src: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
{ src: "/icons/icon-512.png", sizes: "512x512", type: "image/png" }
]
};PWA push notifications let the ERP communicate with field workers without requiring them to keep the app open. Use cases: a new work order has been assigned, a delivery route has changed, a customer has been called to confirm an appointment, inventory at a pickup location has been updated. Implementing push notifications requires: the Push API (for receiving server-sent notifications), the Notifications API (for displaying them), and a notification server (Firebase Cloud Messaging is the standard choice). Users must grant notification permission — design the permission request to explain why notifications are valuable before asking.
Offline PWAs store data on the device. If a field worker's device is lost or stolen, the cached data — including customer information, pricing, work order details — is potentially accessible. For ERP data that includes sensitive financial or personal information, implement device-level encryption requirements and remote wipe capability (via mobile device management software) before deploying an offline-capable field PWA. Also implement a session timeout that clears the local cache after a configurable idle period.
Indonesian mobile networks vary dramatically by location. Your field PWA must perform on 3G connections (10–50 Mbps effective throughput in rural areas) as well as 4G. Optimize for network performance: compress all images (WebP format), lazy-load non-critical resources, use HTTP/2 or HTTP/3 on the server, and implement a request queue in the service worker that batches sync operations rather than making one API call per record. Target a First Contentful Paint of under 2 seconds on a 3G connection. Test on a physical Android device with network throttling, not just a desktop Chrome DevTools simulation.
PWA is excellent for form-based field workflows (logging completions, recording deliveries, capturing signatures). It struggles with hardware integration (Bluetooth scanners, NFC readers, specialized printers) — these require native app or React Native. It also struggles with very complex UIs that users expect to feel native — a basic form works; a complex drag-and-drop production scheduling interface does not. For Indonesian field workers whose primary need is logging work, submitting forms, and accessing reference data, PWA covers 95% of the use case at a fraction of native app cost. Know the 5% and plan accordingly.