Combining Sync Strategies for Seamless User Experience
To create a truly offline-first experience, we need to combine Background Sync and IndexedDB
to ensure users never lose data.
1. Syncing Data When the User Returns Online
A common approach is to listen for network status changes and sync data automatically:
window.addEventListener("online", async () => {
console.log("Back online! Syncing data...");
await syncFormData();
});
This ensures that any pending actions are completed as soon as an internet connection is restored.
2. Implementing a Cache-First Strategy for Faster Data Access
Instead of always making network requests, we can cache API responses and serve them from IndexedDB when offline:
async function fetchWithCache(url) {
try {
const response = await fetch(url);
const data = await response.json();
await saveDataLocally(url, data);
return data;
} catch (error) {
console.log("Fetching from IndexedDB:", url);
return await getDataFromIndexedDB(url);
}
}
This technique ensures:
- ✅ Faster page loads.
- ✅ Reduced dependency on network availability.
- ✅ Better user experience when switching between offline and online states.
3. Handling Conflict Resolution for Offline Changes
If a user makes edits while offline, we must handle conflicts when syncing back to the server. One approach is versioning:
- Each update includes a timestamp (
updated_at
). - When syncing, compare timestamps with the server.
- If a conflict arises, prompt the user to choose the correct version.
Example Rails model with versioning:
class Post < ApplicationRecord
validates :updated_at, presence: true
end
And in the client, we send the timestamp with updates:
async function syncPost(post) {
const response = await fetch(`/posts/${post.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(post)
});
if (!response.ok) {
console.warn("Conflict detected! Resolving...");
// Handle merge logic here
}
}
This prevents data loss when multiple versions exist.