Production Checklist
Before shipping your Syncraft Labs–powered application to production, walk through each item below. These recommendations come from real-world patterns observed in large-scale, offline-capable apps.
1. Configure Outbox Size Limits & Overflow Strategy
Every call to update() appends an OutboxEntry to IndexedDB. If users stay offline for hours (or days), the outbox can grow without bound — eventually exhausting the browser’s storage quota.
Set maxOutboxSize and overflowStrategy explicitly:
const store = createSyncStore<AppState>({ storageKey: "orders", initialState: { orders: [] }, maxOutboxSize: 500, // default is 1000 overflowStrategy: "dropOldest", // "reject" | "dropOldest" | "forceFlush" onOverflow: ({ outboxSize, maxOutboxSize }) => { console.warn(`Outbox overflowed: ${outboxSize}/${maxOutboxSize}`); },});Overflow Strategies
| Strategy | Behavior | Best for |
|---|---|---|
"reject" (default) | Throws an Error when full, preventing new writes | Critical data where no mutation can ever be lost |
"dropOldest" | Discards the oldest outbox entry with a warning and saves the newest | High-frequency sensor data, telemetry, draft autosaves |
"forceFlush" | Invokes onOverflow callback to attempt an emergency push before deciding | Background sync with active network retry fallback |
Guideline: Set
maxOutboxSizeto a value that balances offline productivity with storage constraints. For most apps, 200–1000 entries is reasonable.
2. Handle Errors Gracefully
Syncraft Labs uses an optimistic update with pessimistic rollback strategy. When an IndexedDB write fails:
- The in-memory state is rolled back to the previous value.
- All subscribers are re-notified with the reverted state.
- The error is captured in the
errorfield returned byuseSync.
Always render the error state in your UI:
const { data, update, error } = useSync<AppState>("dashboard", opts);
return ( <div> {error && ( <Toast type="error"> Save failed: {error.message}. Your changes have been reverted. </Toast> )} {/* rest of UI */} </div>);For React apps, wrap your tree in an Error Boundary to catch unexpected throws. See the Error Handling guide for complete patterns.
3. Monitor IndexedDB Storage Quota
Browsers enforce storage quotas. When the quota is exceeded, IndexedDB writes fail and Syncraft Labs will roll back the optimistic update.
Check available storage proactively:
async function checkStorageQuota() { if ("storage" in navigator && "estimate" in navigator.storage) { const { usage, quota } = await navigator.storage.estimate(); const usedMB = (usage ?? 0) / (1024 * 1024); const quotaMB = (quota ?? 0) / (1024 * 1024); const percentUsed = ((usage ?? 0) / (quota ?? 1)) * 100;
console.log(`Storage: ${usedMB.toFixed(1)}MB / ${quotaMB.toFixed(1)}MB (${percentUsed.toFixed(1)}%)`);
if (percentUsed > 80) { // Warn user or trigger outbox drain console.warn("Storage quota is running low!"); } }}Request persistent storage to prevent eviction:
async function requestPersistentStorage() { if (navigator.storage && navigator.storage.persist) { const granted = await navigator.storage.persist(); console.log(`Persistent storage ${granted ? "granted" : "denied"}`); }}Why? Without persistent storage, browsers may silently evict IndexedDB data under storage pressure (especially Safari and Firefox). Requesting persistence ensures user data survives.
4. Secure Your fetcher and pusher
In production, every API call needs proper authentication. Inject auth tokens into your fetcher and pusher:
function useAuthSync<T extends Record<string, unknown>>(key: string, opts: UseSyncOptions<T>) { const { getAccessToken } = useAuth(); // your auth hook
return useSync<T>(key, { ...opts, fetcher: opts.fetcher ? async () => { const token = await getAccessToken(); const res = await fetch("/api/data", { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error(`Fetch failed: ${res.status}`); return res.json(); } : undefined, pusher: opts.pusher ? async (entries) => { const token = await getAccessToken(); const res = await fetch("/api/sync", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(entries), }); if (!res.ok) throw new Error(`Sync failed: ${res.status}`); } : undefined, });}Important: Never store auth tokens in Syncraft stores. Tokens should live in secure, httpOnly cookies or a dedicated auth manager — not in IndexedDB.
5. Require HTTPS
IndexedDB is available over HTTP in development, but production apps must use HTTPS for:
- Service Worker support (required for full offline capability)
- Persistent storage (
navigator.storage.persist()requires a secure context) - BroadcastChannel works on HTTP but cross-tab sync is only meaningful in secure contexts
Ensure your deployment serves over HTTPS. All major hosts (Vercel, Netlify, Cloudflare) provide HTTPS by default.
6. Content Security Policy
If your app uses a strict CSP, ensure that worker-src and script-src allow blob: URIs if you plan to use Web Workers alongside Syncraft. The core library itself does not use workers, but your pusher implementation might.
Content-Security-Policy: default-src 'self'; script-src 'self'; worker-src 'self' blob:;7. Monitoring and Observability
Subscribe to store changes for logging and telemetry:
import { createSyncStore } from "@syncraft-labs/core";
const store = createSyncStore<AppState>({ storageKey: "critical-data", initialState: defaultState,});
// Monitor outbox growthsetInterval(async () => { const outbox = await store.getOutbox(); if (outbox.length > 100) { analytics.track("outbox_growth_warning", { count: outbox.length, oldestEntry: outbox[0]?.timestamp, }); }}, 60_000);
// Monitor state changesstore.subscribe((state) => { analytics.track("state_updated", { storageKey: "critical-data", timestamp: Date.now(), });});8. Ensure Supported State Shapes
Syncraft Labs state must be composed of plain objects, arrays, and primitive values:
- Dates: Allowed as leaf values only. Never mutate a Date instance’s fields in place (e.g. via
date.setFullYear()); replace it with a new Date or use ISO 8601 strings / Unix millisecond timestamps. - Unsupported Types: Custom class instances,
Map,Set,RegExp,Error, and functions are not drafted and will trigger dev-mode errors viavalidateStateShape().
Quick Reference
| Item | Priority | Details |
|---|---|---|
maxOutboxSize | 🔴 Critical | Prevents unbounded outbox growth |
| Error state UI | 🔴 Critical | Show rollback errors to users |
| Supported State Shapes | 🔴 Critical | Plain objects, arrays, primitives, Date leaves only |
| Auth in fetcher/pusher | 🔴 Critical | Secure all API calls |
| HTTPS | 🔴 Critical | Required for persistence APIs |
| Storage quota monitoring | 🟡 Important | Proactive quota checks |
| Persistent storage request | 🟡 Important | Prevent silent data eviction |
| CSP headers | 🟢 Nice-to-have | Only needed for strict CSP policies |
| Telemetry / monitoring | 🟢 Nice-to-have | Production observability |