LiveView and PWA

Introduction A demo of a real-time, collaborative multi-page web app built with Phoenix LiveView. It is designed for offline-first ready; it is packaged as a PWA and uses CRDTs or local state and reactive components. Offline first solutions naturally offloads most of the reactive UI logic to JavaScript. When online, we use LiveView "hooks", while when offline, we render the reactive components. It uses Vite as the bundler. GitHub: Source code We can check it deployed: https://solidyjs-lively-pine-4375.fly.dev/ or Context We want to experiment PWA webapps using Phoenix LiveView. We building a simple two pages webapp: LiveStock. On the first page, we mimic a shopping cart where users can pick items until stock is depleted, at which point the stock is replenished. Every user will see and can interact with this counter LiveFlight. On the second page, we propose an interactive map with a form with two inputs where two users can edit collaboratively a form to display markers on the map and then draw a great circle between the two points. Tech overview Component Role Vite Build and bundling framework SQLite Embedded persistent storage of latest Yjs document Phoenix LiveView UI rendering, incuding hooks PubSub / Phoenix.Channel Broadcast/notifies other clients of updates / conveys CRDTs binaries on a separate websocket (from te LiveSocket) Yjs / Y.Map Holds the CRDT state client-side (shared) Valtio Holds local ephemeral state y-indexeddb Persists state locally for offline mode SolidJS renders reactive UI using signals, driven by Yjs observers Hooks Injects communication primitives and controls JavaScript code Service Worker / Cache API Enable offline UI rendering and navigation by caching HTML pages and static assets Leaflet Map rendering MapTiler enable vector tiles WebAssembly container  high-performance calculations for map "great-circle" routes use Zig code compiled to WASM LiveStock page You have both CRDT-based synchronization (for convergence) and server-enforced business rules (for consistency). We thus have two Layers of Authority: CRDT Sync Layer (Collaborative): Clients and server synchronize using Yjs CRDTs to merge concurrent edits deterministically. Clients can modify their local Y-Doc freely (offline or online). Business Rules Layer (Authoritative): The server is authoritative. It validates updates upon the business logic (e.g., stock validation), and broadcasts the canonical state to all clients. Clients propose changes, but the server decides the final state (e.g., enforcing stock limits). Implementation highlights Offline capabilities: Edits are saved to y-indexeddb and sent later. Synchronization Flow: Client sends all pending Yjs updates on (re)connection. The client updates his local Y-Doc with the server responses. Y-Doc mutations trigger UI rendering, and reciprocally, UI modifications update the Y-Doc and propagate mutations to the server. Server Processing: Merges updates into the SQLite3-stored Y-Doc (using y_ex). Applies business rules (e.g., "stock cannot be negative").Broadcasts the approved state. Clients reconcile local state with the server's authoritative version Data Transport: for the LiveStock page: Use a Phoenix.Channel to transmit the Y-Doc state as binary. This minimises the bandwidth usage and decouples CRDT synchronisation from the LiveSocket. The implementation heavily inspired by the repo https://github.com/satoren/y-phoenix-channel made by the author of y_ex. for the LiveFlight: use LiveSocket as we broadcast a limited amount of data. Component Rendering Strategy: online: use LiveView hooks offline: hydrate the cached HTML documents with reactive JavaScript components Service worker lifecyle When the client code is updated, we get notified: Once we accept the update, the new code is active: Build tool: Vite Since we need to setup a Service Worker, we used Vite and the plugin VitePWA. We let Vite bundle all the code and can remove safely esbuild and tailwindcss. They are now part of Vite. Source code of "vite.config.js" Store managers For the LiveStock page, we used Yjs client-side and y_ex server-side. For the LiveFlight page, we use Valtio as we didn't design the state of this page to survive a network disruption.

May 11, 2025 - 01:12
 0
LiveView and PWA

Introduction

A demo of a real-time, collaborative multi-page web app built with Phoenix LiveView.

It is designed for offline-first ready; it is packaged as a PWA and uses CRDTs or local state and reactive components.

Offline first solutions naturally offloads most of the reactive UI logic to JavaScript.

When online, we use LiveView "hooks", while when offline, we render the reactive components.

It uses Vite as the bundler.

GitHub: Source code

We can check it deployed: https://solidyjs-lively-pine-4375.fly.dev/ or

Context

We want to experiment PWA webapps using Phoenix LiveView.

We building a simple two pages webapp:

  • LiveStock. On the first page, we mimic a shopping cart where users can pick items until stock is depleted, at which point the stock is replenished. Every user will see and can interact with this counter

Image description

  • LiveFlight. On the second page, we propose an interactive map with a form with two inputs where two users can edit collaboratively a form to display markers on the map and then draw a great circle between the two points.

Image description

Tech overview

Component Role
Vite Build and bundling framework
SQLite Embedded persistent storage of latest Yjs document
Phoenix LiveView UI rendering, incuding hooks
PubSub / Phoenix.Channel Broadcast/notifies other clients of updates / conveys CRDTs binaries on a separate websocket (from te LiveSocket)
Yjs / Y.Map Holds the CRDT state client-side (shared)
Valtio Holds local ephemeral state
y-indexeddb Persists state locally for offline mode
SolidJS renders reactive UI using signals, driven by Yjs observers
Hooks Injects communication primitives and controls JavaScript code
Service Worker / Cache API Enable offline UI rendering and navigation by caching HTML pages and static assets
Leaflet Map rendering
MapTiler enable vector tiles
WebAssembly container  high-performance calculations for map "great-circle" routes use Zig code compiled to WASM

LiveStock page

You have both CRDT-based synchronization (for convergence) and server-enforced business rules (for consistency).
We thus have two Layers of Authority:

  • CRDT Sync Layer (Collaborative):
    Clients and server synchronize using Yjs CRDTs to merge concurrent edits deterministically.
    Clients can modify their local Y-Doc freely (offline or online).

  • Business Rules Layer (Authoritative):
    The server is authoritative. It validates updates upon the business logic (e.g., stock validation), and broadcasts the canonical state to all clients.
    Clients propose changes, but the server decides the final state (e.g., enforcing stock limits).

Implementation highlights

  • Offline capabilities:
    Edits are saved to y-indexeddb and sent later.

  • Synchronization Flow:
    Client sends all pending Yjs updates on (re)connection.
    The client updates his local Y-Doc with the server responses.
    Y-Doc mutations trigger UI rendering, and reciprocally, UI modifications update the Y-Doc and propagate mutations to the server.

  • Server Processing:
    Merges updates into the SQLite3-stored Y-Doc (using y_ex).
    Applies business rules (e.g., "stock cannot be negative").Broadcasts the approved state.
    Clients reconcile local state with the server's authoritative version

  • Data Transport:

    • for the LiveStock page: Use a Phoenix.Channel to transmit the Y-Doc state as binary. This minimises the bandwidth usage and decouples CRDT synchronisation from the LiveSocket. The implementation heavily inspired by the repo https://github.com/satoren/y-phoenix-channel made by the author of y_ex.
    • for the LiveFlight: use LiveSocket as we broadcast a limited amount of data.
  • Component Rendering Strategy:

    • online: use LiveView hooks
    • offline: hydrate the cached HTML documents with reactive JavaScript components

Service worker lifecyle

When the client code is updated, we get notified:

Image description

Once we accept the update, the new code is active:

Image description

Build tool: Vite

Since we need to setup a Service Worker, we used Vite and the plugin VitePWA.
We let Vite bundle all the code and can remove safely esbuild and tailwindcss. They are now part of Vite.

Source code of "vite.config.js"

Store managers

For the LiveStock page, we used Yjs client-side and y_ex server-side.

For the LiveFlight page, we use Valtio as we didn't design the state of this page to survive a network disruption.