react-offline-kit
v1.0.2
Published
React + Vite template for offline-first apps — IndexedDB, service worker, background sync, and conflict resolution.
Maintainers
Readme
react-offline-kit
React + Vite starter for Progressive Web Apps that work fully offline and sync when connected.
IndexedDB · Service Worker · Sync Queue · Conflict Resolution
Clone this. Build your app. It works offline on day one.
When to use this
Use react-offline-first when:
- You're building a PWA (Progressive Web App) that must work without a network connection
- Your users are in the field, on mobile, or in low-connectivity environments (field tools, inspection apps, rural apps)
- You need local-first data — changes write instantly to IndexedDB, sync to your API when online
- You want conflict resolution built in, not bolted on
Not the right fit if your app is purely server-driven with no offline requirement — a standard React + fetch setup is simpler. This template adds real complexity; only use it when offline support is a core feature requirement.
Why not Workbox?
Workbox (Google's service worker toolkit) is excellent for caching strategies. It handles what gets cached — app shell, images, API responses.
react-offline-first solves a different layer: data mutations while offline. When a user creates, updates, or deletes a record with no connection, Workbox doesn't queue that write and replay it later. This template does — via a sync queue in IndexedDB that flushes automatically when the connection returns. Use Workbox for cache strategies; use this template for offline-capable CRUD.
What's included
| File | What it does |
|------|-------------|
| src/db/indexedDB.js | Clean async wrapper over browser IndexedDB |
| src/sync/syncEngine.js | Flush queue to your API, resolve conflicts |
| src/hooks/useOfflineData.js | CRUD hook backed by IndexedDB + auto-sync |
| src/hooks/useOnlineStatus.js | Tracks online/offline state |
| src/hooks/useSyncQueue.js | Exposes queue count, sync trigger, last-synced |
| public/sw.js | Service worker — caches app shell for full offline |
| src/App.jsx | Working example: offline-first todo list |
Install
Option A — Use as a template (start a new project):
git clone https://github.com/iamadhitya1/react-offline-first
cd react-offline-first
npm install
npm run devOption B — Add to an existing React project:
npm install react-offline-kitThen import the hooks you need:
import { useOfflineData } from 'react-offline-kit'
import { useOnlineStatus } from 'react-offline-kit'
import { useSyncQueue } from 'react-offline-kit'Core hook — useOfflineData
import { useOfflineData } from 'react-offline-first'
function TodoList() {
const { records, loading, add, update, remove } = useOfflineData('todos')
const handleAdd = async () => {
await add({ text: 'Buy milk', done: false })
// Saved to IndexedDB instantly.
// Queued for cloud sync. Synced when online.
}
return records.map(todo => <div key={todo.id}>{todo.text}</div>)
}Every add, update, remove:
- Writes to IndexedDB immediately — no waiting
- Pushes to sync queue
- Flushes to your API if online — silently queues if not
- Auto-flushes when connection is restored
Sync Engine
import { configureSyncEngine } from 'react-offline-kit'
configureSyncEngine({
apiBase: 'https://your-api.com/api',
conflictStrategy: 'newer-wins', // 'client-wins' | 'server-wins' | 'newer-wins' | fn
getHeaders: () => ({
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`,
}),
onSyncComplete: ({ synced, failed }) => {
console.log(`Synced ${synced}, failed ${failed}`)
},
onConflict: ({ local, server, resolved }) => {
console.log('Conflict resolved:', resolved)
},
})Conflict strategies
| Strategy | Behaviour |
|----------|-----------|
| newer-wins | Whichever record has the higher updatedAt wins (default) |
| client-wins | Local change always overwrites server |
| server-wins | Server version replaces local on conflict |
| (local, server) => record | Custom function — full control |
Sync status UI
import { useSyncQueue } from 'react-offline-first'
function SyncBadge() {
const { isOnline, pendingCount, syncing, sync } = useSyncQueue()
return (
<div>
{isOnline ? '🟢 Online' : '🔴 Offline'}
{pendingCount > 0 && ` · ${pendingCount} unsynced`}
{isOnline && pendingCount > 0 && (
<button onClick={sync}>{syncing ? 'Syncing…' : 'Sync now'}</button>
)}
</div>
)
}API contract
The sync engine expects your API to follow this pattern:
GET /api/{collection}/{id} → 200 { ...record } | 404
POST /api/{collection} → 201 { ...record }
PUT /api/{collection}/{id} → 200 { ...record }
DELETE /api/{collection}/{id} → 204Works with any backend — Express, FastAPI, Supabase Edge Functions, etc.
Service Worker
The included sw.js caches the app shell (HTML, JS, CSS) so the app loads instantly even with no network. API calls are never cached — only the static app shell.
Registered automatically in App.jsx:
navigator.serviceWorker.register('/sw.js')Author
M. Adhitya — Founder, Rewrite Labs
License
MIT © 2025 M. Adhitya
Built at Rewrite Labs
