offline-first
Offline-first patterns. Service Workers, Background Sync, IndexedDB, cache strategies, conflict resolution.
소스 정보
- 저장소
- skeletorflet/opencode-kit
- 최근 소스 활동
- 2026년 4월 3일 15:32
- 감지된 SKILL.md 언어
- 영어
- 스타
- 10
- 포크
- 1
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
SKILL.md 표시 중
SKILL.md
소스 지침 · 읽기 전용 미리보기- name
- offline-first
- description
- Offline-first patterns. Service Workers, Background Sync, IndexedDB, cache strategies, conflict resolution.
# Offline-First
> Design for offline. Online is an enhancement.
---
## 1. Cache Strategies
| Strategy | How | Use For |
|----------|-----|---------|
| **Cache First** | Cache → Network (stale fallback) | Static assets, fonts |
| **Network First** | Network → Cache on fail | API data, dynamic content |
| **Stale While Revalidate** | Cache (immediately) + refresh in bg | Feeds, listings |
| **Cache Only** | Cache only, no network | App shell |
| **Network Only** | Network only, no cache | Real-time, sensitive |
---
## 2. Service Worker Setup
```ts
// sw.ts (Workbox)
import { precacheAndRoute, cleanupOutdatedCaches } from "workbox-precaching";
import { registerRoute } from "workbox-routing";
import { StaleWhileRevalidate, NetworkFirst, CacheFirst } from "workbox-strategies";
import { BackgroundSyncPlugin } from "workbox-background-sync";
cleanupOutdatedCaches();
precacheAndRoute(self.__WB_MANIFEST); // App shell
// Static assets: Cache First
registerRoute(
({ request }) => request.destination === "image",
new CacheFirst({
cacheName: "images",
plugins: [new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 30 * 86400 })],
})
);
// API: Network First
registerRoute(
({ url }) => url.pathname.startsWith("/api/"),
new NetworkFirst({
cacheName: "api-cache",
networkTimeoutSeconds: 3,
plugins: [new ExpirationPlugin({ maxAgeSeconds: 60 * 60 })],
})
);
```
---
## 3. Background Sync
```ts
// Queue failed requests for later
const bgSyncPlugin = new BackgroundSyncPlugin("mutations-queue", {
maxRetentionTime: 24 * 60, // 24 hours
});
registerRoute(
({ url }) => url.pathname.startsWith("/api/mutations"),
new NetworkOnly({ plugins: [bgSyncPlugin] }),
"POST"
);
```
```ts
// Manual background sync
navigator.serviceWorker.ready.then(sw => {
return sw.sync.register("sync-messages");
});
// In SW
self.addEventListener("sync", (event) => {
if (event.tag === "sync-messages") {
event.waitUntil(syncPendingMessages());
}
});
```
---
## 4. IndexedDB for Local Data
```ts
import { openDB } from "idb";
const db = await openDB("myapp", 1, {
upgrade(db) {
const store = db.createObjectStore("todos", { keyPath: "id" });
store.createIndex("synced", "synced");
},
});
// Write locally (optimistic)
async function addTodo(todo: Todo) {
const localTodo = { ...todo, synced: false, localId: crypto.randomUUID() };
await db.add("todos", localTodo);
// Sync in background
if (navigator.onLine) {
await syncTodo(localTodo);
}
}
// Get unsynced on reconnect
window.addEventListener("online", async () => {
const unsynced = await db.getAllFromIndex("todos", "synced", false);
await Promise.all(unsynced.map(syncTodo));
});
```
---
## 5. Conflict Resolution Strategies
| Strategy | Description | Use When |
|----------|-------------|---------|
| **Last Write Wins** | Latest timestamp wins | Simple, low conflict |
| **Server Wins** | Server always authoritative | Financial, inventory |
| **Client Wins** | Client always wins | User preferences |
| **Manual merge** | Present conflict to user | Documents, collaborative |
| **CRDTs** | Conflict-free data structures | Real-time collab |
```ts
// Last Write Wins with timestamp
type SyncedEntity = {
id: string;
data: unknown;
updatedAt: Date;
localUpdatedAt: Date;
};
function resolveConflict(server: SyncedEntity, local: SyncedEntity) {
return local.localUpdatedAt > server.updatedAt ? local : server;
}
```
---
## 6. Offline UX Patterns
```
Status indicators:
├── Online/offline banner (subtle, dismissible)
├── "Changes saved locally" indicator
├── "Syncing..." when reconnected
└── "Sync failed" with retry button
What works offline:
├── Reading cached content
├── Creating/editing (queue for sync)
├── Search (local index)
└── Settings changes
What requires online:
├── Authentication
├── Payment
├── Real-time features
└── Sharing/collaboration
```
---
## 7. Testing Offline
```
Chrome DevTools:
Network tab → Offline checkbox
Application → Service Workers → Offline
Simulate slow network:
Network → Throttling → Slow 3G
Test scenarios:
├── Start offline (nothing cached yet)
├── Go offline after loading (has cache)
├── Create data offline, come back online
└── Conflict: edit on two devices offline
```
GitHub에서 보기