Phoenix LiveView vs REST API for Real-Time Dashboards: A Decision Guide
Decision guide for building a real-time Phoenix dashboard: compare LiveView server rendered versus REST API plus SPA, with trade-offs, constraints, and a concrete LiveView sketch for validation.
30 Apr 2026, 07:41 UTC

The decision in practice
Building an interactive real-time dashboard in Phoenix forces a choice between server rendered LiveView and a traditional controller plus REST API with client JavaScript. The useful takeaway is: choose LiveView when the team is Elixir centric, real time updates are core to the product, and you want to keep domain logic in one language. Choose REST plus SPA when you need stateless horizontal scale without per-user server memory, rich client libraries, or SEO-first public pages.
Constraints that drive the decision are multi node horizontal scaling, limited browser memory on clients, SEO for public pages, and operational simplicity without a separate SPA build pipeline.
Decision context and constraints
LiveView holds a BEAM process per socket. A BEAM process is the lightweight Erlang VM process that backs each LiveView connection. That process stores assigns, the server side state rendered to the browser.
Phoenix.PubSub is the built in message bus for broadcasting events between nodes. It is required for pushing updates to LiveView clients.
Key constraints:
- Memory per connection on the server, not the client.
- WebSocket connection limits per node and OS file descriptors.
- SEO needs prerender or static fallback because LiveView sends an initial HTML payload then hydrates.
- PubSub adapter choice affects node discovery. The PG adapter uses Postgres and has different semantics than Redis.
Options compared
| Option | Pros | Cons |
|---|---|---|
| LiveView server rendered | Minimal client JS, Elixir only business logic, built in PubSub push, easy auth via session | WebSocket per user, server memory per socket, harder SEO without prerender |
| REST API plus SPA | Stateless scale, rich client libraries, SEO via SSR frameworks | Duplicate validation, CORS auth complexity, separate JS build and team |
Trade offs for dashboard workloads
LiveView keeps domain logic in Elixir, improving consistency and testability. Server diffs reduce network chattiness compared to polling REST endpoints. The cost is per connection memory and CPU for diffing, and reliance on Phoenix.PubSub for broadcast.
REST plus SPA offloads rendering to clients and scales horizontally without holding state. It introduces network chattiness, eventual UI consistency, and operational overhead of two codebases.
For dashboards with frequent small updates, filtering, and server authoritative data, LiveView reduces client memory and avoids duplicating validation rules.
Concrete LiveView implementation sketch
Define a LiveView that loads initial assigns in mount and handles filtering via handle_event. Use PubSub to push updates.
defmodule MyAppWeb.DashboardLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, data: load_data())}
end
def handle_event("filter", params, socket) do
{:noreply, assign(socket, data: filter(params["q"]))}
end
endBroadcast updates from a background process:
Phoenix.PubSub.broadcast(MyApp.PubSub, "dashboard:updates", {:update, payload})Subscribe in mount and handle info messages to update assigns. Keep assigns small. For heavy lists use LiveView streams to avoid copying large data into assigns.
Validation and limits
Limitations to plan for:
- LiveView state lives in a BEAM process per socket. Large assigns increase memory and GC pressure.
- WebSocket limits require channel sharding and load balancer sticky sessions.
- Public pages need prerender or static fallback for SEO.
Practical checks:
- Create a minimal phx.new app, generate a live view, run mix phx.server in the project root as a developer with write permission, and open two browser tabs to confirm diff updates propagate instantly.
- Deploy two nodes behind a load balancer with sticky sessions and publish a PubSub message from one node to verify both nodes receive it.
- Profile memory per LiveView process under simulated load with :observer to confirm assigns size stays bounded.
These checks do not prove production readiness but confirm the core behaviors: server diffing, PubSub propagation across nodes, and per socket memory growth.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.