LiveSvelte supports server-side rendering (SSR) of Svelte components, which provides meaningful HTML on the first paint before the JavaScript bundle loads.
How SSR Works
When SSR is active, the initial (dead) render calls Node.js to execute Svelte's render() function server-side. The result is embedded directly in the HTML response:
- LiveSvelte calls
SSR.render(component_name, props, slots) render()returns%{"head" => "<style>...</style>", "html" => "<div>...</div>", "css" => %{"code" => "..."}}- The
headand CSS styles are included in the page;htmlis placed inside thedata-svelte-targetdiv - When JavaScript loads, the
SvelteHookhydrates the existing DOM instead of mounting fresh
SSR Modes
LiveSvelte has two SSR modules for different environments:
NodeJS Mode (Production)
Uses elixir-nodejs to run a pool of Node.js workers that execute the SSR bundle.
# config/prod.exs
config :live_svelte,
ssr_module: LiveSvelte.SSR.NodeJS,
ssr: trueThe SSR bundle is built by:
mix assets.build # runs phoenix_vite.npm vite build (client + SSR)
This produces priv/svelte/server.js, which the NodeJS supervisor loads on application start.
ViteJS Mode (Development)
Forwards SSR requests to the Vite dev server over HTTP. This provides instant HMR without rebuilding the SSR bundle on every change.
# config/dev.exs
config :live_svelte,
ssr_module: LiveSvelte.SSR.ViteJS,
vite_host: "http://localhost:5173"ViteJS Mode Requires Vite Dev Server
LiveSvelte.SSR.ViteJS only works when mix phx.server is running alongside the Vite dev server started by Phoenix's watchers. If the Vite server is not running, SSR will silently fall back to client-only rendering.
Configuration
Enable/disable SSR globally:
# config/config.exs
config :live_svelte, ssr: true # enabled (default)
config :live_svelte, ssr: false # disabledSelect SSR module:
config :live_svelte, ssr_module: LiveSvelte.SSR.NodeJS # production (default)
config :live_svelte, ssr_module: LiveSvelte.SSR.ViteJS # developmentPer-Component SSR Opt-Out
Disable SSR for a specific component:
<.svelte name="HeavyChart" props={%{data: @data}} socket={@socket} ssr={false} />Components with ssr={false} render a loading slot or nothing on the first paint, then mount client-side normally.
HMR in Development
When running with LiveSvelte.SSR.ViteJS, changes to Svelte files trigger automatic hot module replacement. The SvelteHook re-mounts affected components without a full page reload.
With phoenix_vite (recommended): The layout uses PhoenixVite.Components.assets. The Igniter installer adds to your endpoint in config/dev.exs a :vite watcher and static_url: [host: "localhost", port: 5173], so mix phx.server starts the Vite dev server and asset URLs point at it — Svelte and CSS then hot-reload with no extra terminal. See Installation for the exact config; if you added phoenix_vite or LiveSvelte manually, add that endpoint config yourself.
Without phoenix_vite: Use LiveSvelte.Reload.vite_assets/1 in your layout to include Vite's HMR client and production fallback:
<LiveSvelte.Reload.vite_assets assets={["/js/app.js"]}>
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}></script>
</LiveSvelte.Reload.vite_assets>Loading Slot
Show content while a component is loading (only when ssr={false}):
<.svelte name="SlowChart" props={%{data: @data}} socket={@socket} ssr={false}>
<:loading>
<div class="spinner">Loading chart...</div>
</:loading>
</.svelte>Loading Slot + SSR Incompatible
The :loading slot is mutually exclusive with SSR. Using both together produces a compile warning. If SSR is active, the loading slot is ignored.
Telemetry
LiveSvelte emits telemetry events for SSR operations:
| Event | When |
|---|---|
[:live_svelte, :ssr, :start] | SSR render begins |
[:live_svelte, :ssr, :stop] | SSR render completes; includes duration_microseconds measurement |
[:live_svelte, :ssr, :exception] | SSR render throws an exception |
Attach handlers for observability:
:telemetry.attach(
"live-svelte-ssr",
[:live_svelte, :ssr, :stop],
fn _event, measurements, _metadata, _config ->
Logger.debug("SSR render took #{measurements.duration_microseconds}µs")
end,
nil
)Testing with SSR
SSR is disabled in the test environment by default. To write tests that verify SSR output, enable it per test suite:
defmodule MyAppWeb.SsrTest do
use MyAppWeb.ConnCase, async: false # must be async: false
setup do
Application.put_env(:live_svelte, :ssr, true)
on_exit(fn -> Application.put_env(:live_svelte, :ssr, false) end)
:ok
end
test "renders SSR HTML", %{conn: conn} do
html = conn |> get("/counter") |> html_response(200)
assert html =~ "data-ssr=\"true\""
end
endUse get/2 + html_response/2 for initial HTML checks — visit/2 from PhoenixTest connects the socket and transitions past the dead render.
Production Deployment
See Deployment for complete Node.js setup instructions for production.