Skip to content

Instantly share code, notes, and snippets.

@MatejBransky
Created June 19, 2026 13:28
Show Gist options
  • Select an option

  • Save MatejBransky/d8ffb989ba99469ac22f130da2efde34 to your computer and use it in GitHub Desktop.

Select an option

Save MatejBransky/d8ffb989ba99469ac22f130da2efde34 to your computer and use it in GitHub Desktop.
Hybrid local-first app

Architectural Concept: Hybrid Local-First Dashboard

Goal: Achieve instant UI responsiveness and eliminate complex frontend cache management (TanStack/RTK Query) by using a sync engine (PowerSync + SQLite), without the complexity and risks associated with full offline-mode capabilities.


💡 The Core Concept

The application leverages a local-first architecture for extreme performance but retains a server-first logic for conflict resolution. Instead of supporting long-term offline work (e.g., working on an airplane), the app expects an active internet connection, shrinking the conflict window to the exact same size as a standard REST API.


⚡ Why It Beats Traditional Server-First (REST/GraphQL)

1. The Death of "Cache Invalidation"

  • Traditional Approach: When mutating data, developers must manually invalidate cache keys for other tabs, lists, and charts so the user sees the fresh state.
  • Hybrid Local-First: Mutations are written directly to the local SQLite database. Reactive queries (PowerSync/TanStack DB) instantly detect the change and automatically redraw all affected components across the entire app.

2. Instant Browsing (Zero Loading Spinners)

  • Navigating between dashboard tabs or pages doesn't trigger network fetches; data is read directly from the local DB. Transitions are instantaneous (0ms latency)—no UI flickering, no waiting.

🤝 Handling Conflicts & Offline States (UX Rules)

The low risk of conflicts is maintained through strict yet user-friendly handling of the connection state:

A. Reading Data (Browsing)

  • Users can freely browse already synchronized data residing in the local DB.
  • If the network drops, the app does not block the user (no aggressive full-screen overlays).
  • Transparency: If the sync engine fails to connect to the server for longer than 30 seconds, a subtle banner appears in the header:
    ⚠️ Viewing offline data (Last updated X min ago).

B. Writing Data (Mutations)

  • The user makes a change $\rightarrow$ thanks to optimistic UI, it reflects on the screen instantly and enters the upload queue.
  • If the app is offline and the queue cannot be processed within a timeout (e.g., 30–60s):
    • Option 1 (Strict/Safe): Mutation controls are disabled as soon as the offline banner appears.
    • Option 2 (Flexible): A pending icon ⏳ Waiting to sync appears next to the element. If the sync ultimately fails, a warning/exclamation mark is shown, prompting the user to retry (or a rollback occurs).
  • Server-Side Conflicts: If two online users edit the same field within seconds of each other, standard server-side Last-Write-Wins (LWW) applies—exactly like a classic web app.

🛡️ Key Arguments for Team Discussion

  • For Business/Management: From a data integrity and conflict perspective, we risk nothing more than we do with our current REST API. If the backend goes down, the app simply doesn't write, which is the accepted industry standard.
  • For Developers: We write code against a local database. No more writing custom fetchers, managing loading states for every component, or orchestrating global state sync.
  • For Users: An extremely fast application with the snappiness of desktop software (like Linear or Notion) that gracefully survives micro-disconnects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment