DEV Community

Cover image for Cross-Session State in Phoenix LiveView: Designing for Shared Presence and Real-Time Sync
HexShift
HexShift

Posted on

Cross-Session State in Phoenix LiveView: Designing for Shared Presence and Real-Time Sync

Phoenix LiveView shines at building real-time interfaces without JavaScript, but one of the most powerful — and often overlooked — patterns is its ability to synchronize state across multiple sessions or users. While LiveView’s default model is user-centric and session-local, sometimes your application needs to go a step further: syncing interface state across users, broadcasting changes between views, and creating shared real-time experiences.

This isn't just about presence or chat. Think of scenarios like collaborative editing, multi-user dashboards, admin monitoring panels, or live voting displays. In each of these, what one user does must affect what others see — and it has to happen in real time, without reloading the page or writing a single line of JavaScript.

The key to unlocking this capability is understanding how Phoenix’s PubSub system integrates with LiveView. LiveView tracks process state per socket, but PubSub allows you to go beyond a single connection. It gives you a distributed, scalable way to push updates to all relevant LiveViews, even across nodes in a cluster.

To implement cross-session synchronization, you’ll typically introduce a few concepts. First, you define a topic namespace for whatever shared state you’re modeling — it might be tied to a document ID, a project, a team, or some other logical grouping. Next, you use Phoenix.PubSub to allow LiveViews to subscribe to and broadcast changes on that topic. When a LiveView mounts, it subscribes to the topic, and any relevant changes coming from other users will be pushed down to it via messages. Inside the LiveView, you handle those messages and update the socket assigns, triggering a re-render.

Let’s say you’re building a live presence list for an admin dashboard. When a new user connects, you want other users on the same dashboard to see that presence change reflected immediately. You can model this using Phoenix Presence and broadcast any presence diffs to all dashboard LiveViews subscribed to a common topic. Each LiveView receives those diffs and updates the rendered presence list, giving every user a live, accurate picture of who’s currently online.

Now imagine pushing this further — a shared edit mode where one user updates a form field and other users watching the same form see that change happen in real time. Or a situation where a moderator pauses a live quiz, and all connected viewers immediately see a "paused" banner appear. These aren't theoretical. They are practical, achievable patterns made possible by combining LiveView with PubSub, Presence, and careful design.

Handling shared state responsibly does require some discipline. You need to think about the source of truth. If you're only storing state inside each LiveView socket, you can't coordinate across sessions. So for any data that needs to be shared, you’ll want to extract it into an external process — often a GenServer or ETS-based store — or persist it to the database with broadcast hooks. The LiveViews become subscribers and renderers of that external state, responding to updates rather than initiating them in isolation.

For example, you might have a GenServer managing the state of a shared whiteboard. Each LiveView that mounts joins the appropriate topic, requests the current state, and listens for any broadcasted changes. When a user draws a line, that event is sent to the GenServer, which updates the authoritative state and broadcasts the diff. All connected LiveViews apply the diff locally and re-render the canvas. It feels real-time, but the architecture stays simple and Elixir-native.

This approach becomes even more important in clustered systems. When you deploy your Phoenix app to multiple nodes, each LiveView socket lives in memory on a specific node. You can’t rely on global variables or shared processes unless they’re designed to be distributed. PubSub abstracts away those concerns. Whether you’re running on one server or ten, LiveViews can continue to publish and subscribe to events the same way. Behind the scenes, Phoenix PubSub handles routing those messages where they need to go, across the cluster.

There are some common pitfalls. One is over-broadcasting. If every minor interaction results in a PubSub broadcast, you can create a feedback loop or overwhelm connected clients. Always consider what events truly need to be shared and debounce or throttle where necessary. Another issue is stale state. If a LiveView connects late and the shared state has already changed, you need a way to request the current snapshot — either from the database, from an external process, or via a direct message from another client. Don’t rely on broadcast-only flows for state initialization.

Another consideration is authority. Who gets to write to the shared state? In collaborative environments, it’s often necessary to enforce locks, leases, or ownership constraints. A whiteboard is a great demo, but in production you may need to prevent race conditions or concurrent overwrites. GenServers are useful here because they can enforce a serialized update model — only one change at a time, with logic in place to reject or queue updates based on user role or timing.

Cross-session state isn’t just a technical trick. It unlocks a new class of apps: collaborative tools, live multiplayer experiences, and admin control panels that feel dynamic and responsive. And it does it without requiring client-side frameworks or state hydration libraries. You stay in Elixir. You write testable, inspectable, concurrent code. And your UI stays snappy and focused.

It’s worth saying again: Phoenix LiveView isn’t just for simple apps. When you leverage LiveComponents for encapsulation, PubSub for messaging, and Presence or GenServer processes for state coordination, you can build systems that feel like full-blown SPA applications — but with better runtime guarantees, better visibility, and better maintainability.

If you’re already comfortable building LiveView UIs and are ready to go deeper into the architecture of scalable, multi-user apps, this is the layer you want to learn next. Build that collaborative dashboard. Experiment with presence. Model your shared state in GenServers. Let Phoenix PubSub do the heavy lifting of communication, and focus on shaping the interface your users need.

If you’re building Phoenix LiveView apps that need to scale not just technically but in terms of team and process, I’ve written a detailed PDF guide: Phoenix LiveView: The Pro’s Guide to Scalable Interfaces and UI Patterns. It’s a 20-page manual for designing LiveView apps that are maintainable, testable, and ready for collaboration. Whether you’re solo or part of a team, this guide will help you build LiveView systems that are a joy to work on — today and in the future.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.