PhoenixVapor compiles Vue template syntax into native LiveView rendered trees. Four progressive modes share a common foundation: Vue/Vapor templates and LiveView rendered diffs have the same statics/dynamics shape.

Core: Statics/Dynamics Split

A Vue template compiles into a split — static HTML fragments and dynamic insertion points:

Template:  <div class="card"><h1>{{ title }}</h1><p>{{ body }}</p></div>
Statics:   ["<div class=\"card\"><h1>", "</h1><p>", "</p></div>"]
Dynamics:  [title, body]

This maps directly to %Phoenix.LiveView.Rendered{}:

  • Statics sent once per fingerprint, cached by the client
  • Only changed dynamics travel the wire on updates
  • v-if → nested %Rendered{}
  • v-for%Comprehension{}

Expression Evaluation

Template expressions are evaluated against LiveView assigns:

  • Simple ({{ count }}, item.name) → Elixir map access via OXC AST
  • Complex (.filter(), .map(), arrow functions) → QuickBEAM JS eval
  • Change tracking: each slot knows which assigns it depends on (compile-time AST analysis)

Mode 1: ~VUE Sigil

Vue syntax as a template DSL. Zero client JS.

Template  Vize.vapor_split!  %Rendered{}  LiveView diff  morphdom

Expressions evaluate in Elixir. Events map to phx-click, phx-submit, etc. The browser runs the standard LiveView client — it doesn't know Vue exists.

Mode 2: Reactive (.vue SFC)

Server-side Vue reactivity via QuickBEAM. Zero client JS.

SFC  ScriptSetup.parse  Runtime (GenServer + QuickBEAM)
     Template  %Rendered{} with reactive state from JS runtime

A persistent QuickBEAM context per LiveView holds ref() values, computed() definitions, and function handlers. Vue's @vue/reactivity runs server-side on the BEAM. State survives across events but resets on process restart.

Mode 3: Hybrid (.vue SFC)

Split reactivity — server owns data, client owns UI state.

SFC  Classifier (AST analysis)
     Server: mount/render/handle_event (Elixir)
     Client: Vue 3 component (JS, ~50KB)
     Bridge: LiveView hook syncs props via data attributes

The compiler analyzes <script setup> and classifies each binding:

PatternClassification
defineProps(["x"])Server prop
ref(value)Client ref
computed using any propMixed computed
Function with "use server"Server action
Function writing to a propServer action (auto-detected)
Function writing only to refsClient handler

Server renders full HTML for first paint (SEO). Client hydrates with Vue 3 createApp, taking over reactive slots. Client interactions (search, sort, select) are instant — zero network. Server actions send events over the existing LiveView WebSocket.

Wire Protocol

Initial render: server sends statics + dynamics + props JSON in data-pv-props attribute. Updates: only changed server slots + updated props JSON travel the wire. Client-only changes (typing in search) produce zero wire traffic.

State Sync

  • Server → Client: LiveView assign changes → re-render → diff with props JSON → hook's updated()__applyProps() → Vue reactivity propagates
  • Client → Server: "use server" function → pushEventhandle_event → assign change → back to step 1
  • Client → Client: ref mutation → computed recomputation → Vue re-render. No wire.

Custom Elixir Code

A hybrid module is a standard LiveView. The use PhoenixVapor macro generates render/1 and fallback handle_event/3 stubs (via @before_compile) — everything else is yours to define. User-defined handle_event clauses take precedence over generated fallbacks.

The "use server" directive in the .vue file serves two purposes:

  1. Tells the client codegen to generate a pushEvent call for that function name
  2. Registers the event name so a fallback handle_event is generated if the developer doesn't write one

The developer writes the actual server logic in Elixir:

def handle_event("deleteContact", %{"id" => id}, socket) do
  Repo.delete!(Contact, id)
  {:noreply, assign(socket, contacts: Repo.all(Contact))}
end

All standard LiveView callbacks work: mount/3, handle_info/2, handle_params/3, terminate/2. PubSub subscriptions, presence, streams — the full LiveView toolkit is available.

Mode 4: Full Vue Runtime

Third-party Vue component libraries rendered server-side in QuickBEAM.

SFC + bundle  VueRuntime (GenServer + QuickBEAM + lexbor DOM)
              HTML string  %Rendered{}  LiveView diff

Full Vue semantics: provide/inject, component composition, ARIA attributes. Used for libraries like Reka UI.

Module Map

Template / Render

Reactive

Hybrid

Full Runtime

Client JS

  • priv/js/hybrid-bridge.js — LiveView hook for hybrid mode
  • priv/js/vue-reactivity.js@vue/reactivity for server-side reactive mode
  • priv/js/runtime-setup.js — QuickBEAM reactive runtime bootstrap