PH

Phoenix LiveView

web-framework v1.0+

Phoenix LiveView

Version: 1.0+

Overview

Phoenix LiveView is the real-time UI technology that powers all Prismatic Platform dashboards. It enables rich, interactive user experiences by maintaining a persistent WebSocket connection between the server and browser, rendering HTML on the server and efficiently patching the DOM with minimal data transfer. This architecture eliminates the need for a separate JavaScript frontend framework while delivering performance characteristics that match or exceed traditional single-page applications for the platform's use cases.

The Prismatic Platform uses LiveView extensively for its security monitoring dashboards, agent status displays, EASM (External Attack Surface Management) interfaces, and compliance assessment tools. Every interactive view in the platform -- from the Perimeter dashboard showing real-time security ratings to the agent constellation display tracking 404+ agents -- is implemented as a LiveView. The server-side rendering approach means that complex business logic (security calculations, compliance scoring, agent coordination) stays in Elixir where it benefits from the BEAM's concurrency model, rather than being duplicated in JavaScript.

LiveView's integration with Phoenix PubSub enables multi-user real-time collaboration features where changes made by one user are instantly visible to all connected users. This is critical for the platform's team-based security operations, where multiple analysts may be examining the same attack surface simultaneously and need to see discoveries as they happen.

Key Features

  • Server-Side Rendering: HTML rendered on the server with a minimal JavaScript client (~30KB) that handles DOM patching and WebSocket communication
  • Real-Time Updates: Automatic DOM patching through WebSocket connection -- only changed elements are transmitted, minimizing bandwidth
  • Streams: Efficient rendering of large collections (agent lists, vulnerability tables) without full re-render using append/prepend operations
  • Function Components: Stateless components for reusable UI elements with compile-time slot validation and HEEx template syntax
  • Live Components: Stateful components with their own lifecycle for encapsulating complex interactive widgets
  • Uploads: Built-in file upload handling with progress tracking, drag-and-drop support, and server-side validation
  • JS Hooks: Client-side JavaScript interop for third-party libraries like Three.js and Chart.js
  • Live Navigation: SPA-like navigation with live_patch and live_redirect maintaining the WebSocket connection across page transitions

Platform Integration

LiveView powers all interactive dashboards across the platform. The Perimeter EASM dashboard demonstrates the typical pattern: mount loads initial data, PubSub subscription enables real-time updates, and stream operations handle efficient list rendering.

defmodule PrismaticWeb.PerimeterLive.Index do
  use PrismaticWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket) do
      Phoenix.PubSub.subscribe(PrismaticWeb.PubSub, "perimeter:updates")
      Phoenix.PubSub.subscribe(PrismaticWeb.PubSub, "perimeter:findings")
    end

    assets = PrismaticPerimeter.list_assets()
    rating = PrismaticPerimeter.current_rating()

    {:ok,
     socket
     |> assign(security_rating: rating, page_title: "Perimeter Dashboard")
     |> stream(:assets, assets)}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_filters(socket, params)}
  end

  @impl true
  def handle_info({:asset_discovered, asset}, socket) do
    {:noreply, stream_insert(socket, :assets, asset, at: 0)}
  end

  @impl true
  def handle_info({:rating_changed, new_rating}, socket) do
    {:noreply, assign(socket, :security_rating, new_rating)}
  end

  @impl true
  def handle_event("filter_assets", %{"type" => type}, socket) do
    filtered = PrismaticPerimeter.list_assets(type: type)
    {:noreply, stream(socket, :assets, filtered, reset: true)}
  end
end

LiveView components encapsulate reusable UI patterns like the security rating badge used across multiple dashboards:

defmodule PrismaticWeb.Components.SecurityRating do
  use Phoenix.Component

  attr :grade, :atom, required: true
  attr :score, :integer, required: true
  attr :class, :string, default: ""

  def security_badge(assigns) do
    ~H"""
    <div class={"rounded-lg p-4 #{grade_bg(@grade)} #{@class}"}>
      <span class="text-4xl font-black text-white"><%= @grade %></span>
      <span class="text-sm text-white/80 ml-2"><%= @score %>/900</span>
    </div>
    """
  end

  defp grade_bg(:A), do: "bg-green-600"
  defp grade_bg(:B), do: "bg-blue-600"
  defp grade_bg(:C), do: "bg-yellow-600"
  defp grade_bg(:D), do: "bg-orange-600"
  defp grade_bg(:F), do: "bg-red-600"
end

Architecture

LiveView sits at the intersection of several platform technologies, serving as the primary interface between users and the platform's backend capabilities.

LayerTechnologyRole in LiveView Stack
BrowserJavaScript client (phoenix_live_view.js)DOM patching, event capture, WebSocket management
TransportWebSocketsPersistent bidirectional connection
Server ProcessLiveView GenServerState management, event handling, template rendering
MessagingPhoenix PubSubCross-process real-time event delivery
StylingTailwindCSS + FlowbiteUtility-first CSS with component library
InteractivityAlpine.jsClient-side state for dropdowns, modals, toggles
3D VisualizationThree.jsWebGL scenes managed via JS Hooks
DataEcto + PostgreSQLPersistent data queries and mutations

Each LiveView process consumes approximately 40-60KB of memory on the BEAM, meaning the platform can support thousands of concurrent dashboard sessions on a single node.

Lifecycle and State Management

Understanding the LiveView lifecycle is essential for building performant, correct dashboard implementations. Every LiveView undergoes a two-phase mount: the initial disconnected mount renders static HTML for fast first-paint and SEO, followed by a connected mount that establishes the WebSocket connection and enables real-time interactivity.

The lifecycle callbacks form a predictable state machine:

CallbackPhasePurposePlatform Usage
mount/3Both phasesInitialize state, subscribe to topicsLoad dashboard data, PubSub subscriptions
handle_params/3ConnectedURL parameter changesFilter, sort, and pagination state
handle_event/3ConnectedUser interactionsButton clicks, form submissions, filter changes
handle_info/2ConnectedExternal messagesPubSub updates, timer ticks, background job results
render/1Both phasesTemplate renderingHEEx template with Tailwind classes

The assigns map is the single source of truth for a LiveView's state. The platform follows a strict convention: only data that the template directly renders should be stored in assigns. Derived values are computed in the template or in helper functions, never cached in assigns where they could become stale.

Streams for Large Collections

LiveView streams are the platform's primary tool for rendering large, dynamic collections without the memory overhead of holding all items in server-side assigns. The agent monitoring dashboard, for instance, displays hundreds of active agents with real-time status updates. Without streams, each status update would re-render the entire agent list. With streams, only the changed agent row is updated in the DOM.

# Efficient agent list with stream-based updates
def mount(_params, _session, socket) do
  agents = PrismaticAgents.list_active()
  {:ok, stream(socket, :agents, agents)}
end

def handle_info({:agent_status_changed, agent}, socket) do
  # Only this single agent row is re-rendered and patched
  {:noreply, stream_insert(socket, :agents, agent)}
end

def handle_info({:agent_removed, agent}, socket) do
  {:noreply, stream_delete(socket, :agents, agent)}
end

Security Architecture

LiveView's server-side architecture provides significant security advantages for a platform that handles sensitive intelligence data. Because all state management and business logic execute on the server, there is no client-side JavaScript bundle that could expose API endpoints, data transformation logic, or authorization rules to inspection. The browser receives only rendered HTML and minimal DOM patches, meaning an attacker examining the client-side code gains no insight into the platform's internal data structures or processing pipeline.

This security-by-architecture approach eliminates an entire class of client-side vulnerabilities including XSS attacks on sensitive data rendered in JavaScript state stores. It is a natural fit for a security intelligence platform where data confidentiality is paramount.

Performance Characteristics

LiveView's server-rendered architecture provides predictable performance characteristics that align with the platform's strict page load requirements.

MetricTargetTypicalNotes
Initial page load<250ms80-150msServer-side render + first paint
LiveView mount<150ms50-100msWebSocket upgrade + initial assign
Event handling<50ms5-20mshandle_event callback execution
DOM patch sizeMinimal200B-2KBOnly changed elements transmitted
Memory per connection~50KB40-60KBBEAM process + assigns state
Concurrent connections10,000+Tested to 5,000Per node with 4GB RAM
Reconnection time<2s~500msAutomatic reconnect on network interruption

Configuration

# config/config.exs
config :prismatic_web, PrismaticWeb.Endpoint,
  live_view: [signing_salt: "generated_salt"],
  pubsub_server: PrismaticWeb.PubSub

# config/dev.exs - LiveView development settings
config :prismatic_web, PrismaticWeb.Endpoint,
  live_reload: [
    patterns: [
      ~r"priv/static/.*(js|css|png|jpeg|svg)$",
      ~r"lib/prismatic_web/(controllers|live|components)/.*(ex|heex)$"
    ]
  ]

# config/prod.exs - Production WebSocket settings
config :prismatic_web, PrismaticWeb.Endpoint,
  live_view: [signing_salt: System.fetch_env!("LIVE_VIEW_SALT")],
  check_origin: ["https://prismatic-prod.fly.dev", "https://prismatic-reality.com"]

Testing LiveView

The platform tests all LiveView modules using the Phoenix.LiveViewTest library, which simulates the full WebSocket lifecycle including mount, event handling, and PubSub message delivery. This testing approach validates both the rendered HTML and the state transitions.

defmodule PrismaticWeb.PerimeterLiveTest do
  use PrismaticWeb.ConnCase, async: true
  import Phoenix.LiveViewTest

  test "displays security rating on mount", %{conn: conn} do
    {:ok, view, html} = live(conn, "/perimeter")
    assert html =~ "Security Rating"
    assert has_element?(view, "[data-role=security-grade]")
  end

  test "updates when rating changes", %{conn: conn} do
    {:ok, view, _html} = live(conn, "/perimeter")
    send(view.pid, {:rating_changed, %{grade: :A, score: 850}})
    assert render(view) =~ "A"
  end

  test "filters assets by type", %{conn: conn} do
    {:ok, view, _html} = live(conn, "/perimeter")
    view |> element("[data-role=filter-domains]") |> render_click()
    assert has_element?(view, "[data-role=asset-row]")
  end
end

Best Practices

  • Guard PubSub subscriptions with connected?/1 -- subscribing during the static render phase (disconnected mount) causes errors and duplicate messages
  • Use streams for large collections -- rendering 1,000+ items with standard assigns causes expensive full re-renders; streams append/prepend efficiently
  • Keep assigns minimal -- only assign data that the template actually renders; large unused data structures waste memory across all connected sessions
  • Debounce rapid events -- form inputs should use phx-debounce="300" to prevent flooding the server with keypress events
  • Handle stale state gracefully -- when a user clicks an action on an item that was concurrently deleted, handle the {:error, :not_found} case instead of crashing
  • Use live_patch for filter changes -- URL-based state management through handle_params/3 enables bookmarkable filtered views and browser back/forward navigation
  • Test with Phoenix.LiveViewTest -- the testing library simulates WebSocket connections and enables assertions on rendered HTML and event handling

Comparison with Alternatives

FeaturePhoenix LiveViewReact/Next.jsVue/NuxtHTMX
Server renderingNativeSSR optionalSSR optionalServer-driven
JavaScript required~30KB client100KB+ framework80KB+ framework~14KB
State managementServer-side assignsClient-side (Redux, etc.)Client-side (Pinia, etc.)Server-side
Real-time updatesBuilt-in (WebSocket)Requires additional setupRequires additional setupSSE/WebSocket
SEOExcellent (server HTML)Requires SSR setupRequires SSR setupExcellent
Offline supportLimitedFull PWA supportFull PWA supportLimited
Learning curveLow (Elixir knowledge)Medium-HighMediumLow
Platform fitNative BEAM integrationSeparate frontend serviceSeparate frontend servicePartial integration

The Prismatic Platform chose LiveView because it eliminates the operational complexity of maintaining a separate frontend application while providing real-time capabilities natively. The server-side rendering model ensures that security-sensitive business logic never leaves the server.

  • Phoenix Framework - The base web framework providing routing, endpoints, and channels
  • Phoenix PubSub - Real-time messaging backbone for cross-process event delivery
  • WebSockets - Transport protocol for persistent bidirectional communication
  • Alpine.js - Client-side interactivity complement for dropdowns, modals, and toggles
  • TailwindCSS - UI styling framework used exclusively in all LiveView templates
  • Flowbite - Component library providing pre-built UI patterns
  • ETS - In-memory storage frequently accessed from LiveView processes

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL