tutorial

How to Monitor LiveReact with Vigilmon

Monitor LiveReact hybrid Phoenix LiveView + React applications — track component render health, WebSocket reconnect rates, and bidirectional state sync with external uptime checks and heartbeat monitoring.

How to Monitor LiveReact with Vigilmon

LiveReact bridges two worlds that rarely talk directly: Phoenix LiveView's server-driven real-time UI and React's vast ecosystem of client components. When you embed a React component inside a LiveView template, state flows in both directions — LiveView pushes updates down via WebSocket and React events propagate back up through pushEvent. The result is powerful, but it introduces a new class of failure that neither standard LiveView monitoring nor React error boundaries catch on their own.

If the WebSocket drops, your React components lose their state sync channel. If a LiveView mount fails, the React tree never renders. If a hydration mismatch occurs after a hot deploy, users see a frozen component with no error message. You need external monitoring to know when these failures are happening in production.

This tutorial covers:

  • A health check endpoint that validates both LiveView socket state and React asset serving
  • HTTP uptime monitoring with Vigilmon
  • Heartbeat monitoring for LiveView processes that drive React components
  • Alerts for WebSocket disconnects and hydration failures

Why Monitor LiveReact?

LiveReact failures are subtle because the app appears partially functional:

| Failure mode | Symptom without monitoring | |---|---| | LiveView WebSocket disconnect | React component freezes; no server-side updates | | JavaScript bundle 404 after deploy | React tree never mounts; blank component area | | Hook registration mismatch | LiveReact hooks silently not called on client | | Server-side LiveView crash | React component stuck on last known state | | pushEvent queue overflow | Client events dropped; state diverges from server | | SSL/CDN misconfiguration | Static assets timeout; React never loads |


Step 1: Health check endpoint

Add a plug that checks database connectivity, LiveView socket acceptability, and static asset serving in one response:

# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
  import Plug.Conn

  def init(opts), do: opts

  def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
    checks = %{
      database: check_database(),
      live_view_socket: check_live_view_socket(),
      assets: check_assets()
    }

    status = if Enum.all?(checks, fn {_, v} -> v == :ok end), do: 200, else: 503

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(status, Jason.encode!(%{
        status: if(status == 200, do: "ok", else: "degraded"),
        checks: checks
      }))
    |> halt()
  end

  def call(conn, _opts), do: conn

  defp check_database do
    case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do
      {:ok, _} -> :ok
      _ -> :error
    end
  end

  defp check_live_view_socket do
    # Verify the LiveView endpoint process is alive and accepting connections
    endpoint = MyAppWeb.Endpoint
    if Process.whereis(endpoint), do: :ok, else: :error
  end

  defp check_assets do
    # Check that the app.js bundle hash file exists (written by mix assets.deploy)
    manifest_path = Path.join(:code.priv_dir(:my_app), "static/cache_manifest.json")
    if File.exists?(manifest_path), do: :ok, else: :error
  end
end

Wire it in before the router so it bypasses authentication plugs:

# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.HealthCheck
plug MyAppWeb.Router

Test locally:

mix phx.server
curl http://localhost:4000/health
# {"status":"ok","checks":{"database":"ok","live_view_socket":"ok","assets":"ok"}}

Step 2: Track React component lifecycle via telemetry

LiveReact emits JavaScript events when hooks connect and disconnect. On the Elixir side, add a LiveView hook that pings a GenServer counter so you can expose the active component count:

# lib/my_app/live_react_monitor.ex
defmodule MyApp.LiveReactMonitor do
  use GenServer

  def start_link(_), do: GenServer.start_link(__MODULE__, %{active: 0, errors: 0}, name: __MODULE__)

  def component_mounted,   do: GenServer.cast(__MODULE__, :mounted)
  def component_unmounted, do: GenServer.cast(__MODULE__, :unmounted)
  def component_error,     do: GenServer.cast(__MODULE__, :error)

  def stats, do: GenServer.call(__MODULE__, :stats)

  @impl true
  def init(state), do: {:ok, state}

  @impl true
  def handle_cast(:mounted,   s), do: {:noreply, %{s | active: s.active + 1}}
  def handle_cast(:unmounted, s), do: {:noreply, %{s | active: max(0, s.active - 1)}}
  def handle_cast(:error,     s), do: {:noreply, %{s | errors: s.errors + 1}}

  @impl true
  def handle_call(:stats, _from, state), do: {:reply, state, state}
end

Add it to your supervision tree:

# lib/my_app/application.ex
children = [
  MyApp.Repo,
  MyAppWeb.Endpoint,
  MyApp.LiveReactMonitor,  # ← add
]

Call component_mounted/0 and component_unmounted/0 from your LiveView mount/3 and terminate/2 callbacks in views that host React components.

Update your health plug to include the component stats:

defp check_live_view_socket do
  stats = MyApp.LiveReactMonitor.stats()
  # Flag if error rate exceeds 10% of total mounts
  if stats.errors < 5, do: :ok, else: :degraded
end

Step 3: Set up HTTP monitoring in Vigilmon

Point Vigilmon at your health endpoint:

  1. Sign up at vigilmon.online
  2. Click New Monitor → HTTP
  3. Enter https://yourdomain.com/health
  4. Set check interval: 1 minute
  5. Add a keyword check for "ok" so degraded responses also trigger an alert
  6. Save

Also add a second monitor for your static asset CDN path:

  • URL: https://yourdomain.com/assets/app.js
  • Expected status: 200
  • Keyword check: omit (binary response is fine)

This catches deploy-time CDN purge failures that leave React bundles returning 404.


Step 4: Heartbeat monitoring for LiveView processes

Long-running LiveView processes that push state to React components can hang silently — the process is alive but has stopped processing events. Add a periodic self-check:

# lib/my_app_web/live/dashboard_live.ex
defmodule MyAppWeb.DashboardLive do
  use MyAppWeb, :live_view

  @heartbeat_interval :timer.minutes(5)
  @heartbeat_url System.get_env("VIGILMON_DASHBOARD_HEARTBEAT_URL")

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket) do
      schedule_heartbeat()
      MyApp.LiveReactMonitor.component_mounted()
    end
    {:ok, socket}
  end

  @impl true
  def terminate(_reason, _socket) do
    MyApp.LiveReactMonitor.component_unmounted()
    :ok
  end

  @impl true
  def handle_info(:heartbeat, socket) do
    ping_vigilmon()
    schedule_heartbeat()
    {:noreply, socket}
  end

  # ... your existing handle_event and handle_info clauses

  defp schedule_heartbeat do
    Process.send_after(self(), :heartbeat, @heartbeat_interval)
  end

  defp ping_vigilmon do
    if @heartbeat_url do
      Task.start(fn ->
        :httpc.request(:get, {String.to_charlist(@heartbeat_url), []}, [{:timeout, 5000}], [])
      end)
    end
  end
end

In Vigilmon, create a Heartbeat Monitor:

  1. Go to Monitors → New → Heartbeat
  2. Set the grace period to 10 minutes (2× your heartbeat interval)
  3. Copy the ping URL into VIGILMON_DASHBOARD_HEARTBEAT_URL

If your LiveView process crashes and the supervisor can't restart it, the heartbeat stops and Vigilmon alerts you.


Step 5: Key metrics to alert on

| Metric | Alert threshold | Why | |---|---|---| | /health HTTP status | Non-200 for 2 consecutive checks | Full stack availability | | /assets/app.js HTTP status | Non-200 | React bundle unreachable | | Keyword "degraded" in /health body | Any match | Component error rate elevated | | Dashboard LiveView heartbeat | Grace period exceeded | LiveView process hung | | Response time on /health | > 3 s | Database or endpoint congestion |


Step 6: Status page

Add your LiveReact service to a public status page:

  1. In Vigilmon, go to Status Pages → New
  2. Add both HTTP monitors (health endpoint and assets) and the LiveView heartbeat
  3. Name them clearly: "App (Web)", "Static Assets (React)", and "Dashboard (LiveView)"
  4. Share the URL with your on-call team

Conclusion

LiveReact gives you the best of LiveView server logic and the React component ecosystem, but failures span both the BEAM process layer and the JavaScript client layer. With Vigilmon you get:

  • HTTP uptime checks that catch server-side LiveView crashes and CDN asset failures
  • Keyword checks that surface degraded states before they become full outages
  • Heartbeat monitors that detect hung LiveView processes driving your React components

Start with the health endpoint and a single heartbeat for your most-used LiveView, then expand coverage as your component tree grows.

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →