We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
LiveView Performance: Sub-250ms Page Loads
Performance engineering techniques for Phoenix LiveView applications
Prismatic Engineering
Prismatic Platform
The 250ms Budget
The Prismatic Platform enforces a strict performance budget: every page must
load in under 250 milliseconds, with server-side rendering completing in under
100ms and LiveView mounts finishing in under 150ms. These are not aspirational
targets -- they are enforced by performance gates in CI.
Achieving these targets across 30+ LiveView pages requires systematic
optimization at every layer.
Mount Optimization
The mount/3 callback is the most critical performance path. Every millisecond
spent here delays the initial page render. The key principles:
Do the minimum in mount, defer the rest:
def mount(_params, session, socket) do
socket =
socket
|> assign_defaults(session)
|> assign(:loading, true)
|> assign(:data, [])
if connected?(socket) do
send(self(), :load_data)
end
{:ok, socket}
end
def handle_info(:load_data, socket) do
data = fetch_data()
{:noreply, assign(socket, data: data, loading: false)}
end
This pattern renders a loading skeleton on the initial static render, then
populates data over the WebSocket. Users see content in two phases but the
perceived load time drops significantly.
Use try/rescue for production resilience:
def mount(params, session, socket) do
try do
{:ok, do_mount(params, session, socket)}
rescue
e in [Ecto.QueryError, ArgumentError] ->
Logger.error("Mount failed: #{Exception.message(e)}")
{:ok, assign(socket, :error, "Failed to load page")}
end
end
Never let a mount crash take down the LiveView process. Catch specific
exceptions and render an error state instead.
ETS Caching for Render Data
Data that changes infrequently (navigation items, feature flags, configuration)
should be cached in ETS rather than fetched on every mount:
defmodule PrismaticWeb.Cache.NavItems do
@table :nav_items_cache
@ttl_ms :timer.minutes(5)
def get_nav_items do
case :ets.lookup(@table, :items) do
[{:items, items, inserted_at}] ->
if System.monotonic_time(:millisecond) - inserted_at < @ttl_ms do
items
else
refresh_and_return()
end
[] ->
refresh_and_return()
end
end
end
This eliminates database queries for data that changes once every few minutes.
Lazy Loading Patterns
Large datasets should never be loaded all at once. The platform uses three
lazy loading strategies:
Paginated Loading
def handle_event("load-more", _params, socket) do
page = socket.assigns.page + 1
new_items = fetch_page(page, socket.assigns.per_page)
{:noreply, assign(socket, items: socket.assigns.items ++ new_items, page: page)}
end
Tab-Based Loading
Only load data for the active tab. When the user switches tabs, fetch that
tab's data:
def handle_event("switch-tab", %{"tab" => tab}, socket) do
data = load_tab_data(tab)
{:noreply, assign(socket, active_tab: tab, tab_data: data)}
end
Viewport-Based Loading
Use a JavaScript hook to detect when elements enter the viewport and trigger
server-side data loading via pushEvent.
WebSocket Optimization
LiveView communicates over WebSockets. Every assign/2 call that changes a
value triggers a diff to be sent to the client. Minimize unnecessary assigns:
# Avoid: assigns unchanged data on every tick
def handle_info(:tick, socket) do
{:noreply, assign(socket, data: fetch_data(), timestamp: DateTime.utc_now())}
end
# Better: only assign if data actually changed
def handle_info(:tick, socket) do
new_data = fetch_data()
if new_data != socket.assigns.data do
{:noreply, assign(socket, data: new_data, timestamp: DateTime.utc_now())}
else
{:noreply, socket}
end
end
Measuring Performance
The platform uses :telemetry to measure mount times and render durations:
:telemetry.execute(
[:prismatic, :liveview, :mount],
%{duration: duration_ms},
%{view: __MODULE__, action: :mount}
)
These measurements feed into performance dashboards and CI gates that reject
deployments exceeding the 250ms budget.
Results
With these patterns applied consistently, the Prismatic Platform achieves:
|--------|--------|--------|