Uživatel otevře vaši aplikaci v metru, v letadle, na chatě bez signálu — a vidí spinner. Nebo prázdnou obrazovku. Nebo chybovou hlášku. V roce 2026 je tohle neakceptovatelné. Offline-first není luxus — je to základ dobré mobilní zkušenosti. A překvapivě, většina týmů to pořád dělá špatně.
Proč offline matters¶
Statistiky jsou jednoznačné: průměrný uživatel stráví 11 % času bez stabilního připojení k internetu. Ne proto, že by žil na vesnici — ale proto, že jezdí metrem, cestuje letadlem, prochází betonovými budovami s mizerným signálem. A v těch momentech vaše aplikace buď funguje, nebo ji uživatel nahradí jinou, která funguje.
Offline-first neznamená „aplikace nějak přežije bez internetu.” Znamená to, že lokální data jsou primární zdroj pravdy a síť je mechanismus synchronizace. Tohle je fundamentální architektonický posun — ne feature, kterou přilepíte na konec.
Pro business aplikace (field service, logistika, inspekce, retail) je offline podpora kritická. Technik v terénu musí zadat data, i když nemá signál. Řidič musí potvrdit doručení v podzemní garáži. Inspektor musí vyfotit závadu v suterénu. Pokud tohle vaše aplikace neumí, máte problém.
Service Workers: základ offline webu¶
Pro PWA a webové aplikace jsou Service Workers klíčová technologie. Service Worker je JavaScript soubor, který běží na pozadí v prohlížeči, nezávisle na hlavní stránce. Funguje jako proxy mezi aplikací a sítí — zachytává HTTP requesty a rozhoduje, jestli je obslouží z cache, ze sítě, nebo kombinací obojího.
`// sw.js — registrace Service Workeru
self.addEventListener(‘install’, (event) => {
event.waitUntil(
caches.open(‘app-shell-v2’).then((cache) => {
return cache.addAll([
’/’,
‘/css/app.css’,
‘/js/app.js’,
‘/offline.html’
]);
})
);
});
self.addEventListener(‘fetch’, (event) => {
event.respondWith(
caches.match(event.request)
.then((cached) => cached || fetch(event.request))
.catch(() => caches.match(‘/offline.html’))
);
});`
Klíčový koncept: Service Worker přežívá zavření tabu. Jednou nainstalovaný zůstává aktivní a může obsluhovat requesty, i když je uživatel offline. To je zásadní rozdíl oproti obyčejnému HTTP cachování — máte nad ním plnou programatickou kontrolu.
IndexedDB vs SQLite: kam ukládat offline data¶
Cache API řeší statické assety. Ale co dynamická data — objednávky, formuláře, produktové katalogy? Tady máte dvě hlavní volby.
IndexedDB¶
Nativní browser API. Asynchronní, transakční NoSQL databáze přímo v prohlížeči. Ideální pro PWA. Kapacita v řádu stovek MB. Přístup přes strukturované klíče a indexy.
SQLite (WASM / native)¶
Plnohodnotná SQL databáze. Pro nativní aplikace (React Native, Flutter, Swift, Kotlin) — přímý přístup. Pro web přes sql.js nebo OPFS. Silné v relačních datech a komplexních dotazech.
Kdy co použít? Pro PWA je IndexedDB přirozená volba — je v prohlížeči, nepotřebuje WASM runtime a má slušnou výkonnost pro většinu use cases. Pro nativní mobilní aplikace je SQLite jasná volba — je rychlejší, má lepší podpora pro transakce a komplexní dotazy, a je to de facto standard v mobilním světě.
`// IndexedDB — uložení offline záznamu
async function saveOfflineRecord(record) {
const db = await openDB(‘fieldwork’, 1, {
upgrade(db) {
const store = db.createObjectStore(‘inspections’, {
keyPath: ‘id’
});
store.createIndex(‘synced’, ‘synced’);
store.createIndex(‘timestamp’, ‘timestamp’);
}
});
await db.put(‘inspections’, {
…record,
id: crypto.randomUUID(),
synced: false,
timestamp: Date.now()
});
}`
Důležitý detail: nikdy nespoléhejte na localStorage pro offline data. Limit 5 MB, synchronní API (blokuje main thread) a žádná podpora pro indexy nebo transakce. localStorage je pro user preferences, ne pro business data.
Sync strategie: optimistic vs pessimistic¶
Nejsložitější část offline-first architektury není ukládání dat — je to jejich synchronizace zpět na server, když se připojení obnoví. Existují dva fundamentální přístupy.
Optimistic sync (offline-first)¶
Uživatel provede akci, aplikace ji okamžitě potvrdí a zápis do lokální databáze. Synchronizace proběhne na pozadí, jakmile je dostupná síť. Pokud vznikne konflikt, řeší se zpětně — buď automaticky (last-write-wins, merge), nebo s uživatelským potvrzením.
Výhody: okamžitá odezva, funguje bez sítě, lepší UX. Nevýhody: konflikty, eventual consistency, složitější error handling. Použití: field service aplikace, poznámky, formuláře, ToDo listy, chat.
Pessimistic sync (online-first)¶
Uživatel provede akci, aplikace čeká na potvrzení serveru a teprve pak aktualizuje UI. Offline queue ukládá operace pro pozdější odeslání, ale neprezentuje je jako potvrzené.
Výhody: konzistence, žádné konflikty, jednodušší implementace. Nevýhody: pomalejší UX, závislost na síti pro potvrzení. Použití: finanční transakce, objednávky, medicínské záznamy — cokoliv, kde eventual consistency není akceptovatelná.
`// Optimistic sync — offline queue s retry
class SyncManager {
async enqueue(operation) {
const queue = await this.getQueue();
queue.push({
id: crypto.randomUUID(),
operation,
timestamp: Date.now(),
retries: 0,
status: ‘pending’
});
await this.saveQueue(queue);
this.attemptSync();
}
async attemptSync() {
if (!navigator.onLine) return;
const queue = await this.getQueue();
for (const item of queue.filter(i => i.status === ‘pending’)) {
try {
await this.sendToServer(item.operation);
item.status = ‘synced’;
} catch (e) {
item.retries++;
if (item.retries >= 3) item.status = ‘failed’;
}
}
await this.saveQueue(queue);
}
}`
Background Sync API¶
Manuální polling a online/offline eventy jsou fragile. Background Sync API je browser API, které řeší synchronizaci elegantně: zaregistrujete sync event v Service Workeru a prohlížeč ho spustí, jakmile má stabilní připojení — i když uživatel aplikaci zavřel.
`// Registrace Background Sync z hlavní aplikace
async function scheduleSync() {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register(‘sync-inspections’);
}
// Service Worker — obsluha sync eventu
self.addEventListener(‘sync’, (event) => {
if (event.tag === ‘sync-inspections’) {
event.waitUntil(syncPendingInspections());
}
});
async function syncPendingInspections() {
const db = await openDB(‘fieldwork’, 1);
const pending = await db.getAllFromIndex(
‘inspections’, ‘synced’, false
);
for (const record of pending) {
const res = await fetch(‘/api/inspections’, {
method: ‘POST’,
body: JSON.stringify(record)
});
if (res.ok) {
record.synced = true;
await db.put(‘inspections’, record);
}
}
}`
Důležité omezení: Background Sync API má zatím plnou podporu pouze v Chromium prohlížečích. Safari podpora je omezená. Pro nativní aplikace používejte platformní ekvivalenty — WorkManager (Android) nebo BGTaskScheduler (iOS).
Cache strategie¶
Výběr správné cache strategie zásadně ovlivňuje výkon i offline chování aplikace. Neexistuje jedna správná strategie — různý typ obsahu vyžaduje různý přístup.
- Cache First: Vždy servíruj z cache, síť použij jen pro aktualizaci. Ideální pro statické assety (CSS, JS, fonty, ikony), které se mění jen při deployi.
- Network First: Zkus síť, při selhání fallback na cache. Pro API data, kde chcete čerstvost, ale tolerujete stale data offline.
- Stale-While-Revalidate: Okamžitě servíruj z cache a zároveň aktualizuj cache ze sítě na pozadí. Nejlepší kompromis mezi rychlostí a čerstvostí. Skvělé pro produktové katalogy, seznamy, profily.
- Network Only: Žádné cachování. Pro data, která musí být vždy aktuální — autentizační tokeny, real-time ceny, transakce.
- Cache Only: Pouze cache, žádná síť. Pro pre-cached assety aplikačního shellu, offline fallback stránky.
`// Stale-While-Revalidate v Service Workeru
self.addEventListener(‘fetch’, (event) => {
if (event.request.url.includes(‘/api/products’)) {
event.respondWith(
caches.open(‘api-cache’).then(async (cache) => {
const cached = await cache.match(event.request);
const fetchPromise = fetch(event.request).then((res) => {
cache.put(event.request, res.clone());
return res;
});
return cached || fetchPromise;
})
);
}
});`
V praxi kombinujete strategie podle typu obsahu. App shell je Cache First, API endpointy jsou Stale-While-Revalidate nebo Network First, a autentizace je Network Only. Klíčové je mít toto explicitně definované v architektonickém dokumentu, ne ad hoc v kódu.
Real-world architektura offline-first aplikace¶
Jak to celé skládáme dohromady? Typická architektura offline-first mobilní aplikace v produkci má tyto vrstvy:
- UI Layer: React Native / Flutter / PWA. Čte data výhradně z lokální databáze. Nikdy přímo ze sítě.
- Local Storage Layer: SQLite (native) nebo IndexedDB (web). Strukturovaná lokální databáze s indexy, transakcemi a migration support.
- Sync Engine: Vlastní nebo knihovní (WatermelonDB, PouchDB, PowerSync). Řeší bidirectional sync, conflict resolution, retry logic a delta sync.
- Network Layer: HTTP/REST nebo GraphQL s offline queue. Interceptor detekuje online/offline stav a routuje requesty.
- Backend API: Serverová strana s podporou pro idempotentní operace, conflict detection (vector clocks, timestamps) a bulk sync endpointy.
Kritický detail, který většina týmů podcení: conflict resolution. Co se stane, když dva uživatelé editují stejný záznam offline? Last-write-wins je nejjednodušší, ale ne vždy správný. Pro komplexní scénáře potřebujete CRDT (Conflict-free Replicated Data Types) nebo field-level merge s uživatelským potvrzením.
Jak to stavíme v CORE SYSTEMS¶
V CORE SYSTEMS dodáváme offline-first mobilní aplikace pro field service, logistiku a inspekční procesy — tedy přesně ty scénáře, kde spolehlivý offline režim není nice-to-have, ale hard requirement.
Náš přístup začíná data flow analýzou: které entity musí být dostupné offline, jaký je expected objem dat, jaké operace se provádějí bez sítě a jaká je tolerance ke konfliktům. Na základě toho navrhujeme sync strategii — většinou kombinujeme optimistic sync pro vytváření nových záznamů s pessimistic přístupem pro editace kritických dat.
Technologicky sáhneme po React Native + WatermelonDB pro nativní aplikace nebo PWA + IndexedDB + Workbox pro webové řešení. Workbox od Googlu je production-grade knihovna pro Service Worker strategie — řeší precaching, runtime caching i Background Sync s minimem boilerplate kódu.
Každá offline-first aplikace, kterou dodáváme, má: sync status indikátor v UI (uživatel vždy ví, jestli je online/offline a kolik operací čeká na sync), conflict resolution UI pro edge cases, automated sync testing v CI/CD pipeline a monitoring dashboard pro sledování sync health v produkci.
Závěr: Offline-first je architektonické rozhodnutí¶
Offline podporu nemůžete přidat na konec vývojového cyklu. Je to architektonické rozhodnutí, které ovlivňuje datový model, sync logiku, UX flow i backend API design. Čím dříve ho uděláte, tím levněji vás vyjde.
Dobrá zpráva: v roce 2026 máme vyspělé nástroje — Service Workers, IndexedDB, Background Sync API, WatermelonDB, Workbox, PowerSync. Technologie není blokátor. Blokátor je architektonické myšlení, které stále předpokládá, že internet je vždy k dispozici. Není.