OndrejValenta

OndrejValenta OP

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 :wink:. 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_svelte interleaves — you render Svelte from Elixir (~V sigil), it does SSR through a Node runtime, props flow through LiveView’s diff on every update. Great for reactive sprinkles.
  • keen_phoenix_svelte isolates — you mount compiled, self-owning apps and talk to them through a narrow boundary. No ~V sigil, 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, JSON data-props); <KeenPhoenixSvelte.runtime> emits a once-per-page context.
  • JS — a KeenApp LiveView hook mounts the island and lazily import()s the bundle; a mountStatic() 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 diffingupdated() skips re-renders when data-props is unchanged.
  • Eager mounting (<.app eager>) — paint an island before the socket connects, then upgrade it with live on 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-end 304 chain browser → Phoenix → origin.
  • Whole multi-file bundlesbase: (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, Vite manifest.json, or our keenManifest() plugin), and any other sub-path is a 404 decided 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.

First Post! Switch mode

OndrejValenta

OndrejValenta OP

I’ve added a new demo (Inline edit · keen_phoenix_svelte) for live editing of page text. It uses Prose based Svelte app as a text/translation editor.

When user is an admin a popup appears over different textblocks

clicking the edit button opens inline text editor.. nothing fancy, still nice.

There is a description how this all work with Live view, for those interested.

— All posts loaded —

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 91561 914
New
byu
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project. My initial shotgu...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
AstonJ
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
mudasobwa
While I am working on the Language Agnostic Code Audit SaaS, which uses MetaAST (spoiler: I am expecting it to be in a good shape for ann...
New

We're in Beta

About us Mission Statement