_CORE

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í.

CORE SYSTEMS — Mobile & Digital tým

Navrhujeme a vyvíjíme mobilní aplikace s offline-first architekturou pro enterprise klienty. Od analýzy přes vývoj po provoz a monitoring.

Další články
Další krok

Potřebujete offline-first mobilní aplikaci?

Začněme analýzou vašich offline požadavků. Navrhneme architekturu, sync strategii a vybereme technologický stack, který bude fungovat v terénu — ne jen v kanceláři.

Domluvme si konzultaci