Jskalc
LiveVue - seamless integration of Vue with Phoenix LiveView
Hi! Today, after a couple weeks of development I’ve released v0.1 of LiveVue.
It’s a seamless integration of Vue and Phoenix LiveView, introducing E2E reactivity of server and client-side state.
Started as a fork of LiveSvelte, evolved to use Vite and a slightly different syntax.
Why you might want to use it?
- Your client-side state grows and it’s hard to deal with it
- You misses declarative rendering on the client side
- You’d like to use a vast ecosystem of Vue libraries
- You’d like to introduce animations
- You like Vue

Why I created it, if LiveSvelte already exists?
- I love Vue and was missing it’s DX and ecosystem (VueUse is amazing)
- More options are always better (right?
) - Vite gives you best-in-class stateful-hot-reload, and paired with stateful-hot reload on the server you have a stateful hot reload across the whole stack (which is HUGE!)
Features:
- E2E reactivity
- Support for
phx-*attributes inside Vue components - Uses Vite for an amazing DX
- Server Side Rendering (optional)
- Vue/Phoenix slots interop
- Vue event handlers can be defined with
JSmodule ~Vsigil for inline Vue component definition
Plans for the future:
- On-demand lazy-loading of components
- Optimised payload with LiveJson or similar
- Better tests
- Dedicated page with examples
- Guide of handling changesets & forms
- Pinia support?

An example
defmodule LiveVueExamplesWeb.LiveCounter do
use LiveVueExamplesWeb, :live_view
def render(assigns) do
~H"""
<.vue
count={@count}
v-component="Counter"
v-socket={@socket}
v-on:inc={JS.push("inc")}
/>
"""
end
def mount(_params, _session, socket) do
{:ok, assign(socket, :counter, 0)}
end
def handle_event("inc", %{"value" => diff}, socket) do
{:noreply, update(socket, :count, &(&1 + diff))}
end
end
<script setup lang="ts">
import {ref} from "vue"
const props = defineProps<{count: number}>()
const emit = defineEmits<{inc: [{value: number}]}>()
const diff = ref<string>("1")
</script>
<template>
Current count
<div class="text-2xl text-bold">{{ props.count }}</div>
<label class="block mt-8">Diff: </label>
<input v-model="diff" class="my-4" type="range" min="1" max="10" />
<button
@click="emit('inc', {value: parseInt(diff)})"
class="bg-black text-white rounded p-2"
>
Increase counter by {{ diff }}
</button>
</template>
You can read some additional details in my short twitter thread.
I’d greatly appreciate a star on the github repo, and giving me any feedback if everything is working ![]()
Most Liked
Jskalc
LiveVue v0.6.0 was released!

Hey everyone! ![]()
I’m excited to announce the release of LiveVue v0.6.0 - this is by far the biggest update since the initial release, with many months of development packed into it. If you’ve been waiting for LiveVue to mature, this is the release that takes it to the next level! ![]()
What’s New?
Massive Performance Boost with JSON Patch Diffs
LiveVue now uses JSON Patch operations to send only the minimal differences when props change. Instead of sending entire prop objects over the WebSocket, only the specific fields that changed are transmitted. This dramatically reduces payload sizes (in many cases over 90%), especially for complex nested structures and lists. Performance of diffing is carefully optimized to be a non-issue.
It works seamlessly with custom structs through the LiveVue.Encoder protocol, designed to be compatible with Jason.Encoder. You can easily implement custom encoders for your own types - for example, here’s how we hide sensitive fields from User struct:
defimpl LiveVue.Encoder, for: MyApp.Accounts.User do
def encode(%MyApp.Accounts.User{} = user, _opts) do
user
|> Map.take([:first_name, :last_name])
|> LiveVue.Encoder.encode(opts)
end
end
Pulling it of required changes to LiveView and multiple PRs to Jsonpatch library.
Complete Documentation Overhaul
The docs have been completely rewritten with comprehensive guides, API reference, and examples. Much easier to get started and go deeper!
Client-side utilities
useLiveNavigation- programmatic navigation withlive_patchandlive_redirectuseLiveEvent- simplified server-pushed event handling with automatic lifecycle management<Link>component - Vue component for LiveView navigation$liveavailable globally in Vue templates - you can now write@click="$live.pushEvent('click')"
TypeScript by Default
The client-side setup is now TypeScript out of the box, with full type safety and autocompletion. The $live property is now automatically available in all Vue templates and fully typed.
Testing Utilities
New LiveVue.Test module provides helpers to inspect Vue component configuration within your LiveView tests.
Migration
If you have an existing setup, there’s a small migration to move from assets/vue/index.js to assets/vue/index.ts and use the new TypeScript setup. The migration guide in the changelog has all the details - it’s straightforward!
What’s Next?
This release really solidifies LiveVue as a mature solution for Vue + LiveView integration. The performance improvements and developer experience enhancements should make it even more enjoyable to work with.
As always, I’d love to hear your feedback and experiences with the new release! And if you find LiveVue useful, a
on the GitHub repo would be much appreciated ![]()
Full Release Notes
Click to expand
Features and Improvements
- JSON Patch Diffs for Props: LiveVue now uses JSON Patch operations to send only the minimal differences when props change, dramatically reducing WebSocket payload sizes. Instead of sending entire prop objects, only the specific fields that changed are transmitted using RFC 6902 JSON Patch format. This optimization works seamlessly with complex nested structures, lists, and custom structs through the
LiveVue.Encoderprotocol. It’s possible to skip diffs by settingv-difftofalseon the component or by settingconfig :live_vue, enable_props_diff: falsein your config. (#60) - New
useLiveNavigationComposable: A newuseLiveNavigationcomposable has been added for programmatic navigation, mirroring the functionality oflive_patchandlive_redirect. (#59). - New
useLiveEventComposable: A newuseLiveEventcomposable has been added to simplify listening to server-pushed events. It automatically manages event listener lifecycle, reducing boilerplate and preventing memory leaks (#58). - New
LinkComponent: A new<Link>Vue component has been added to simplifylive_patchandlive_redirectnavigation within your Vue components. (#47). - TypeScript by Default: The client-side entrypoint at
assets/vue/index.tsis now a TypeScript file by default, improving type safety and the development experience out of the box. $liveTemplate Property: The LiveView hook instance is now automatically available in all Vue templates as the$liveproperty, providing a convenient alternative touseLiveVue(). The property is now also fully typed, providing autocompletion and type checking in your editor. (#56).- Documentation Overhaul: The documentation has been completely rewritten and expanded. It now includes comprehensive guides on architecture, basic and advanced usage, a full client-side API reference, a component reference, and much more. (#49)
- Testing Utilities: The new
LiveVue.Testmodule provides helpers to inspect Vue component configuration (props, slots, event handlers) within your LiveView tests, making it easier to write assertions. (#46) - GitHub CI: A new GitHub Actions workflow has been added to run tests automatically.
Migration Guide
This version transitions the default client-side setup to TypeScript and renames ~V sigil to ~VUE. If you have an existing assets/vue/index.js, follow these steps to upgrade:
-
Rename and replace
index.js:- Delete your existing
assets/vue/index.js. - Create a new file at
assets/vue/index.tswith the following content:
// polyfill recommended by Vite https://vitejs.dev/config/build-options#build-modulepreload import "vite/modulepreload-polyfill" import { Component, h } from "vue" import { createLiveVue, findComponent, type LiveHook, type ComponentMap } from "live_vue" // needed to make $live available in the Vue component declare module "vue" { interface ComponentCustomProperties { $live: LiveHook } } export default createLiveVue({ // name will be passed as-is in v-component of the .vue HEEX component resolve: name => { // we're importing from ../../lib to allow collocating Vue files with LiveView files // eager: true disables lazy loading - all these components will be part of the app.js bundle // more: https://vite.dev/guide/features.html#glob-import const components = { ...import.meta.glob("./**/*.vue", { eager: true }), ...import.meta.glob("../../lib/**/*.vue", { eager: true }), } as ComponentMap // finds component by name or path suffix and gives a nice error message. // `path/to/component/index.vue` can be found as `path/to/component` or simply `component` // `path/to/Component.vue` can be found as `path/to/Component` or simply `Component` return findComponent(components as ComponentMap, name) }, // it's a default implementation of creating and mounting vue app, you can easily extend it to add your own plugins, directives etc. setup: ({ createApp, component, props, slots, plugin, el }) => { const app = createApp({ render: () => h(component as Component, props, slots) }) app.use(plugin) // add your own plugins here // app.use(pinia) app.mount(el) return app }, }) - Delete your existing
-
Update
tsconfig.json:
Addvue/index.tsto theincludearray in yourassets/tsconfig.jsonfile.
Bug Fixes
- SSR Attribute Rendering: Fixed a bug where the
data-ssrattribute was not being rendered correctly as a booleantrueorfalsein the final HTML.
Housekeeping
- Optional Floki Dependency:
flokiis now an optional dependency, only required if you use the new testing utilities. - Dependency Updates: NPM dependencies have been updated to address security vulnerabilities.
- Dropped Elixir 1.12 Support: Support for Elixir 1.12 has been removed to align with Phoenix LiveView’s supported versions.
katafrakt
I seem to be in a minority that thinks that this name is fine. We have LiveSvelte, I’m sure we will soon have LiveReact, LiveSolid, LiveQwik and whatnot. It will hurt the discoverability of this one project and the phonetic similarity is less of a problem when the distinction in written text is clear.
Jskalc
A small update about the project. It’s still growing, both when it comes to adoption and features ![]()
Recently I’ve added a roadmap to the readme of the project. I’m posting it here as well:
Roadmap 
- Add a default handler for Vue emits to eg. automatically push them to the server without explicit
v-onhandlers. - try to use Igniter as a way of installing LiveVue in a project
usePushEvent- an utility similar touseFetchmaking it easy to get data from&handle_event/3 -> {:reply, data, socket}responsesuseLiveForm- an utility to efforlessly use Ecto changesets & server-side validation, similar to HEEXuseEventHandler- an utility automatically attaching & detachinghandleEvent- optimize payload - send only
json_patchdiffs of updated props - VS code extension higlighting
~Vsigil
useLiveForm
I’m working currently on the early version of useLiveForm utility. I’ve posted more details on Twitter, you can find it here.
Popular in Announcing
Other popular topics
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
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










