OndrejValenta
Keen_phoenix_svelte - Our way to integrate Svelte/React/Lit/Plain JS apps as isolated islands in Plain/Live pages
Greetings and salutations!
We’ve been building keen_phoenix_svelte at KeenMate (now v1.0.0-rc.9), and we’d like your feedback, although we know the library is awesome already
. It auto-mounts compiled Svelte 5 apps (or what have you, React, Lit, even plain JS apps) into Phoenix — on both LiveView and plain controller-rendered pages — as self-contained islands.
Live demo & docs: keen-phoenix-svelte.keenmate.dev.
You write this in a template:
<.app name="dashboard" id={"dash-#{@id}"} props={%{org_id: @id}} />
…and the compiled app in assets/apps/dashboard/ is mounted into that element, kept in sync with server state, handed a bridge to the server, and torn down on live navigation — no manual <script>/<link> wiring.
Motivation
In our corporate development, we kept hitting the same shape of problem: a real Svelte app — a dashboard, an org-structure browser with charts, a data grid — that needs to live inside an existing Phoenix app. Not a sprinkle of reactivity on server-rendered markup, but a substantial front-end app with its own state and lifecycle that just needs a clean line back to the server.
People forget that this includes apps that don’t necessarily communicate with the server directly, they, for example, just display documents stored in Oracle HCM, or they communicate with SAP or any other internal service.
We also didn’t want Node in the production render path, didn’t want to reason about SSR hydration mismatches, and we wanted the same component to work on a LiveView route and a plain controller page.
So the model is: the page places an island, hands it config + a connection, then gets out of the way.
How it differs from live_svelte
They sit at opposite ends of the Phoenix+Svelte space, and both are legitimate:
live_svelteinterleaves — you render Svelte from Elixir (~Vsigil), it does SSR through a Node runtime, props flow through LiveView’s diff on every update. Great for reactive sprinkles.keen_phoenix_svelteisolates — you mount compiled, self-owning apps and talk to them through a narrow boundary. No~Vsigil, no server-rendered slots, no SSR / no Node at render time, no hydration-mismatch class of bugs, and plain (non-LiveView) pages are first-class.
Reach for live_svelte when you want Svelte woven into LiveView pages with SSR. Reach for this when the Svelte side is a substantial app that should own its own world.
How it works
Two cooperating halves:
- Elixir —
<.app>renders a hook-bound<div>(phx-update="ignore",data-app, JSONdata-props);<KeenPhoenixSvelte.runtime>emits a once-per-page context. - JS — a
KeenAppLiveView hook mounts the island and lazilyimport()s the bundle; amountStatic()scan mounts islands on plain pages.
Each island gets a small boundary: props (config, not payload), context (user/tokens/csrf/socket), live (LiveView bridge, or null on plain pages), api (REST helper with CSRF + session), channel (Phoenix channel factory), and bus (client-side island-to-island pub/sub). The canonical pattern is if (live) { live.pushEvent(...) } else { api.post(...) } — the same component runs unchanged with or without a LiveView behind it.
Neither half is Svelte-specific: the mount contract is just (target, opts) => { setProps, destroy }, so the demo mounts Svelte, React, Lit and vanilla-JS islands through the one <.app> component.
Performance work
- Only what’s on the page loads — bundles are lazily
import()ed per island. - Automatic page-scoped module preloading —
<.runtime>emits<link rel="modulepreload">for exactly the islands a page mounts, so they download in parallel with the HTML instead of waiting for the socket + hook. - Prop-change diffing —
updated()skips re-renders whendata-propsis unchanged. - Eager mounting (
<.app eager>) — paint an island before the socket connects, then upgrade it withliveon connect. - Placeholder/skeleton cleared the instant the island mounts — no flash of empty container.
Loading islands from elsewhere — proxying & guarding
If you ever had to deal with security and networking issues, and generally with inability to move forward because of CSP, CORS and similar, you’ll appreciate this feature.
You can register an island whose bundle lives on a CDN or another deploy, in :direct mode (browser imports the CDN URL) or :proxy mode (browser imports a same-origin path and Phoenix fetches upstream — no CORS, script-src 'self', the corporate-friendly mode).
The proxy is what we’ve hardened most:
- Revalidating cache — bundles held in ETS (concurrent reads, refc-binary bodies), stale entries do a conditional
GET, concurrent requests for the same URL collapse into one fetch (no stampede), upstream errors serve last-good fail-open. Correct even for unversioned upstreams, with an end-to-end304chain browser → Phoenix → origin. - Whole multi-file bundles —
base:(remote dir) /dir:(local, newest content-hashed entry wins) re-serve JS + CSS + assets same-origin from one registration. - Security — TLS verified against the system trust store (no MITM on the Phoenix→origin leg), no upstream redirects (SSRF guard),
X-Content-Type-Options: nosniff, local sub-paths asserted inside their directory. - Bounded fan-out — concurrent fetches capped (excess sheds
503 + Retry-After), definitive misses negative-cached, so a flood of guaranteed-miss sub-paths can’t drive unbounded outbound requests. manifest:allowlist — register the exact files an app ships (inline list, Vitemanifest.json, or ourkeenManifest()plugin), and any other sub-path is a404decided before any upstream fetch.
Install
{:keen_phoenix_svelte, "~> 1.0"} # pin the exact rc for now — SemVer doesn't match pre-releases
Plus the npm package @keenmate/phoenix_svelte (released in lockstep at the same version) and a Svelte/Vite toolchain.
It’s still 1.0.0-rc, so feedback — especially on the boundary API and the proxy — is very welcome. The example/ app is a full “Teams-style workspace” reference (chat over a channel + Presence, a video catalogue over REST, a calendar, plus Lit/React/vanilla islands). Happy to answer questions here.
Popular in Discussions
Other popular topics
Latest Phoenix Threads
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









