Back to Blog
Engineering March 11, 2026 | 9 min read

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:


MetricTargetActual

|--------|--------|--------|

Page load< 250ms~180ms average Server render< 100ms~65ms average LiveView mount< 150ms~90ms average WebSocket reconnect< 500ms~300ms average

Tags

liveview phoenix performance websocket caching