Deploying Phoenix LiveView: From Setup to Production Readiness
Learn how to mount a LiveView, handle state, and prepare for high‑traffic production with Phoenix 1.7+. The guide covers prerequisites, a step‑by‑step setup, debugging tips, and recovery strategies.
08 Jun 2026, 10:28 UTC

Desired Outcome
By the end of this article you will be able to:
- Generate a new Phoenix 1.7+ project with LiveView support.
- Mount a LiveView module, perform authorization and data loading in
mount/3. - Handle client events with
handle_event/3and update UI state viasocket.assigns. - Configure file uploads and component composition.
- Verify the LiveView traffic flow in dev, run tests, and prepare for high‑concurrency production.
Prerequisites
- Elixir 1.14+ and Erlang/OTP 26+ installed.
- Node.js 18+ for asset compilation.
- PostgreSQL (or another supported DB) for persistence.
- Basic knowledge of Phoenix routing, controllers, and templates.
Step‑by‑Step Setup
- Create a LiveView‑enabled project
mix phx.new my_app --live # cd into the project directory cd my_app # install dependencies mix deps.get # compile assets npm install --prefix assets npm run deploy # start the server mix phx.serverThe
--liveflag adds thephoenix_live_viewdependency and a basic LiveView route inlib/my_app_web/router.ex. - Define a LiveView module
defmodule MyAppWeb.DashboardLive do use Phoenix.LiveView def mount(_params, session, socket) do # Authorization example user_id = session["user_id"] if user_id do # Load initial data {:ok, assign(socket, user: MyApp.Accounts.get_user!(user_id), items: [])} else {:redirect, to: "/login"} end end def handle_event("add_item", %{"name" => name}, socket) do # Persist the item and update assigns {:ok, _item} = MyApp.Items.create(name: name) {:noreply, update(socket, :items, fn items -> [name | items] end)} end endMounting via
live/2in the router:live "/dashboard", DashboardLive - Use HeEx templates for efficient diffing
def render(assigns) do ~H" Hello, <%= @user.name %>! Add-
<%= for item <- @items do %>
- <%= item %> <% end %>
- Configure file uploads
def mount(_params, _session, socket) do {:ok, assign(socket, :uploads, allow_upload(socket, :image, max_entries: 1, accept: ".png,.jpg"))} end # In the template ~H" Upload "Processing after completion:
def handle_event("upload", _params, socket) do consume_uploaded_entries(socket, :image, fn %{path: path} -> # Move to permanent storage File.cp!(path, "/uploads/#{Path.basename(path)}") end) {:noreply, socket} end - Component composition
# Function component (stateless) # lib/my_app_web/components/button.ex defmodule MyAppWeb.Components.Button do use Phoenix.Component def render(assigns) do ~H" <%= @label %> " end end # Usage in a LiveView template ~H" "Stateful live components can be added with
live_component/1when you need isolated state. - Run automated tests
defmodule MyAppWeb.DashboardLiveTest do use MyAppWeb.ConnCase, async: true import Phoenix.LiveViewTest test "renders dashboard", %{conn: conn} do {:ok, view, _html} = live(conn, "/dashboard") assert render(view) =~ "Hello" end endExecute with
mix test. - Verify WebSocket flow in dev
- Open
http://localhost:4000/dashboardin a browser. - Open DevTools → Network → filter by
WS. - Observe the initial GET, the upgrade to a WebSocket, and binary frames containing patch diffs (
phx-update).
- Open
- Prepare for production
- Set
check_origininconfig/prod.exsto match your domain. - Configure a reverse proxy (NGINX, HAProxy) to pass
UpgradeandConnectionheaders. - Adjust BEAM options:
+P 1000000 +Q 1000000for high concurrency. - Use
Phoenix.PubSubfor cross‑process messaging instead of long‑running DB transactions.
- Set
Expected Checks
- LiveView mounts successfully and redirects unauthorized users.
- Client events trigger
handle_event/3and update the DOM without full re‑renders. - File uploads complete with progress events and server‑side storage.
- Component rendering matches expected HTML fragments.
- Automated tests pass in CI without a browser.
- WS traffic shows only patch frames after the initial upgrade.
Recovery Options
- Socket disconnects – the client automatically retries with exponential backoff. Ensure
mount/3is idempotent: re‑load data and re‑authenticate on reconnection. - Memory pressure – monitor process counts with
elixir --sname my_app --remshand:observer.start(). Scale out with more BEAM nodes if+Plimits are reached. - High‑latency networks – use
phx-debouncein the template or implement optimistic updates viaphx-update="ignore"for critical interactions. - Large session payloads – keep session data minimal; store user context in a server‑side store keyed by
user_idand fetch onmount/3. - JavaScript hook failures – wrap hook logic in
try/catchand log errors toconsole.error; fallback to pure LiveView behavior if hooks crash.
Practical Tips
- Use
phx-update="ignore"on elements that should not be patched, reducing diff size. - When a LiveView needs to broadcast updates, call
Phoenix.PubSub.broadcast/3and handlehandle_info/2to update assigns. - For long‑running operations, consider
Task.async_stream/3insidehandle_event/3to keep the socket responsive. - Leverage
LiveViewTesthelpers likeelement/3to query specific DOM nodes before asserting state. - In production, enable
live_reload: falseinconfig/prod.exsto avoid unnecessary reloads and reduce WebSocket chatter.
Conclusion
Phoenix LiveView turns server‑side rendering into an interactive, low‑latency experience without the overhead of a full client‑side framework. By following the steps above, you can quickly prototype, test, and deploy a production‑ready LiveView application that scales with proper BEAM tuning and thoughtful state management.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.