Cloud Sync & Offline
Build offline-first Constructs that automatically sync data to the cloud.
How It Works
Constructs are offline-first by default. Data is stored locally in IndexedDB and optionally synced to the cloud when the user is online.
Local First
All writes go to IndexedDB immediately
Queue Changes
Offline changes are queued for sync
Auto Sync
Syncs automatically when online
Enabling Cloud Sync
Enable sync by passing the sync option to useConstructData:
import { useConstructData } from "@useworkapp/construct-sdk";
// Enable cloud sync
const [data, setData, { status }] = useConstructData(
"my-data",
defaultValue,
{ sync: true } // This enables cloud sync
);
// Sync is debounced by default (1 second)
// Customize the debounce time:
const [data, setData] = useConstructData(
"my-data",
defaultValue,
{ sync: true, syncDebounce: 2000 } // 2 seconds
);Note: Cloud sync requires the user to be signed in with a Netrunner or higher subscription plan.
Handling Sync Status
The hook returns a status field that indicates the current sync state:
const [data, setData, { status, error }] = useConstructData("items", [], { sync: true });
// Status values:
// 'local' - Data saved locally only (sync disabled or user not signed in)
// 'syncing' - Currently uploading changes to cloud
// 'synced' - All changes saved to cloud
// 'offline' - User is offline, changes queued for later
// 'error' - Sync failed (check error for details)
function SyncIndicator({ status, error }: { status: string; error: Error | null }) {
const indicators = {
local: { icon: "💾", text: "Saved locally", color: "text-gray-500" },
syncing: { icon: "🔄", text: "Syncing...", color: "text-blue-500" },
synced: { icon: "☁️", text: "Synced", color: "text-green-500" },
offline: { icon: "📴", text: "Offline", color: "text-yellow-500" },
error: { icon: "⚠️", text: error?.message || "Sync error", color: "text-red-500" },
};
const { icon, text, color } = indicators[status] || indicators.local;
return (
<span className={`text-sm ${color}`}>
{icon} {text}
</span>
);
}Manual Sync Control
Sometimes you want to trigger a sync manually, like before the user leaves:
const [data, setData, { sync, status }] = useConstructData(
"important-data",
defaultValue,
{ sync: true }
);
// Manual sync button
<Button
onClick={() => sync()}
disabled={status === "syncing"}
>
{status === "syncing" ? "Syncing..." : "Sync Now"}
</Button>
// Sync before leaving
useEffect(() => {
const handleBeforeUnload = () => {
if (status !== "synced") {
sync();
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [status, sync]);Offline-First Patterns
Design your UI to work seamlessly offline:
import { useOnlineStatus, useConstructData } from "@useworkapp/construct-sdk";
export default function MyConstruct() {
const isOnline = useOnlineStatus();
const [items, setItems, { status }] = useConstructData("items", [], { sync: true });
return (
<div>
{/* Offline banner */}
{!isOnline && (
<div className="bg-yellow-100 border-b border-yellow-200 p-2 text-sm text-yellow-800">
You're offline. Changes will sync when you reconnect.
</div>
)}
{/* Pending changes indicator */}
{status === "offline" && (
<div className="text-sm text-muted-foreground">
{items.length} changes waiting to sync
</div>
)}
{/* Your app content - works the same online or offline */}
<ItemList items={items} onUpdate={setItems} />
</div>
);
}Background Sync
For standalone PWAs, enable background sync to sync changes even when the app is closed:
import { registerBackgroundSync } from "@useworkapp/construct-sdk";
// Register background sync (call once on app init)
registerBackgroundSync();
// The browser will automatically sync when:
// - The device regains network connectivity
// - The PWA is in the background
// - A Service Worker sync event fires
// Request immediate sync
import { requestImmediateSync } from "@useworkapp/construct-sdk";
// Trigger sync right now (if online)
await requestImmediateSync();Browser Support: Background Sync is supported in Chrome, Edge, and other Chromium-based browsers. Safari and Firefox have limited support.
Conflict Resolution
When the same data is modified on multiple devices, conflicts can occur. The SDK uses version numbers to detect and resolve conflicts:
// Version is automatically tracked
const [data, setData, { version }] = useConstructData("items", []);
// Current conflict resolution strategy: Last Write Wins
// The most recent change (by timestamp) is kept
// Future: Custom conflict resolution
// const [data, setData] = useConstructData("items", [], {
// sync: true,
// onConflict: (local, remote) => {
// // Return the merged result
// return mergeData(local, remote);
// },
// });Sync Quotas & Limits
| Plan | Cloud Storage | Sync Frequency |
|---|---|---|
| Ghost (Free) | No cloud sync | — |
| Netrunner | 100 MB | Real-time |
| Architect | 1 GB | Real-time + Priority |
Best Practices
Always show sync status
Users should know if their data is saved locally, syncing, or synced.
Design for offline first
Your Construct should work perfectly offline. Sync is a bonus, not a requirement.
Handle errors gracefully
Sync can fail. Show a clear message and offer a retry option.
Keep data lean
Don't sync large blobs or files. Keep synced data under a few MB for best performance.