{"id":13490993,"url":"https://github.com/woutdp/live_svelte","last_synced_at":"2026-04-01T18:50:07.850Z","repository":{"id":75926652,"uuid":"604449586","full_name":"woutdp/live_svelte","owner":"woutdp","description":"Svelte inside Phoenix LiveView with seamless end-to-end reactivity","archived":false,"fork":false,"pushed_at":"2026-03-19T20:47:54.000Z","size":20577,"stargazers_count":1637,"open_issues_count":30,"forks_count":74,"subscribers_count":26,"default_branch":"master","last_synced_at":"2026-03-20T10:53:48.765Z","etag":null,"topics":["elixir","liveview","phoenix","phoenix-liveview","ssr","svelte","websocket"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/live_svelte","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/woutdp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"woutdp"}},"created_at":"2023-02-21T04:40:06.000Z","updated_at":"2026-03-19T20:47:57.000Z","dependencies_parsed_at":"2024-04-18T05:31:22.384Z","dependency_job_id":"94bc7644-dc0b-4036-b88d-b042e5b6c7f9","html_url":"https://github.com/woutdp/live_svelte","commit_stats":{"total_commits":282,"total_committers":16,"mean_commits":17.625,"dds":"0.15602836879432624","last_synced_commit":"493a9705756778e1bf8b1c762cfdede01c9fedf1"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/woutdp/live_svelte","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woutdp%2Flive_svelte","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woutdp%2Flive_svelte/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woutdp%2Flive_svelte/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woutdp%2Flive_svelte/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/woutdp","download_url":"https://codeload.github.com/woutdp/live_svelte/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woutdp%2Flive_svelte/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290960,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","liveview","phoenix","phoenix-liveview","ssr","svelte","websocket"],"created_at":"2024-07-31T19:00:52.655Z","updated_at":"2026-04-01T18:50:07.838Z","avatar_url":"https://github.com/woutdp.png","language":"Elixir","funding_links":["https://github.com/sponsors/woutdp"],"categories":["Elixir"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"https://github.com/woutdp/live_svelte/blob/master/logo_3.png?raw=true\" height=\"250\" width=\"250\" \u003e\n\n[![GitHub](https://img.shields.io/github/stars/woutdp/live_svelte?style=social)](https://github.com/woutdp/live_svelte)\n[![Hex.pm](https://img.shields.io/hexpm/v/live_svelte.svg)](https://hex.pm/packages/live_svelte)\n\nSvelte inside Phoenix LiveView with seamless end-to-end reactivity\n\n[Features](#features) •\n[Resources](#resources) •\n[Demo](#demo) •\n[Installation](#installation) •\n[Usage](#usage) •\n[Deployment](#deployment)\n\n\u003c/div\u003e\n\n\u003c!-- MDOC !--\u003e\n\n## Features\n\n-   ⚡ **End-To-End Reactivity** with LiveView\n-   🔋 **Server-Side Rendered** (SSR) Svelte\n-   🪄 **Sigil** as an [Alternative LiveView DSL](#livesvelte-as-an-alternative-liveview-dsl)\n-   ⭐ **Svelte Preprocessing** Support with [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess)\n-   🦄 **Tailwind** Support\n-   💀 **Dead View** Support\n-   🦥 **Slot Interoperability**\n-   📘 **TypeScript** — client assets in TypeScript; public API is type-safe with exported type definitions for consumers\n\n## Resources\n\n-   [HexDocs](https://hexdocs.pm/live_svelte)\n-   [HexPackage](https://hex.pm/packages/live_svelte)\n-   [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view)\n-   [Blog Post](https://wout.space/notes/live-svelte)\n-   [YouTube Introduction](https://www.youtube.com/watch?v=JMkvbW35QvA)\n\n## Demo\n\nFor a full intro and demo check out the [YouTube introduction](https://www.youtube.com/watch?v=JMkvbW35QvA)\n\n`/examples/advanced_chat`\n\nhttps://user-images.githubusercontent.com/3637265/229902870-29166253-3d18-4b24-bbca-83c4b6648578.webm\n\n\u003cbr /\u003e\nSvelte handles the look and feel of the chat, while LiveView takes care of syncing. E2E reactivity to the Svelte component so we don't really need to fetch anything! The 'login' to enter your name is a simple LiveView form. Hybrid!\n\n---\n\n`/examples/breaking_news`\n\nhttps://user-images.githubusercontent.com/3637265/229902860-f7ada6b4-4a20-4105-9ee9-79c0cbad8d72.webm\n\n\u003cbr /\u003e\nNews items are synced with the server while the speed is only client side.\n\n## Why LiveSvelte\n\nPhoenix LiveView enables rich, real-time user experiences with server-rendered HTML.\nIt works by communicating any state changes through a websocket and updating the DOM in realtime.\nYou can get a really good user experience without ever needing to write any client side code.\n\nLiveSvelte builds on top of Phoenix LiveView to allow for easy client side state management while still allowing for communication over the websocket.\n\n### Reasons why you'd use LiveSvelte\n\n-   You have (complex) local state\n-   You want to take full advantage of Javascript's ecosystem\n-   You want to take advantage of Svelte's animations\n-   You want scoped CSS\n-   You like Svelte and its DX :)\n\n## Requirements\n\nFor Server-Side Rendering (SSR) to work you need `node` (version 19 or later) installed in your environment.\n\nMake sure you have it installed in production too. You might be using `node` in the build step, but it might actually not be installed in your production environment.\n\nYou can make sure you have `node` installed by running `node --version` in your project directory.\n\nYou can also use **Bun** instead of Node.js/npm for installing dependencies and running Vite (see [Installation](#installation)); production SSR still uses Node.js.\n\nIf you don't want SSR, you can disable it by not setting `NodeJS.Supervisor` in `application.ex`. More on that in the [SSR](#ssr-server-side-rendering) section of this document.\n\n## Installation\n\n_If you're updating from an older version, make sure to check the `CHANGELOG.md` for breaking changes._\n\nLiveSvelte uses [Vite](https://vite.dev/) for building and [phoenix_vite](https://github.com/LostKobrakai/phoenix_vite) to integrate it with Phoenix. The layout uses `PhoenixVite.Components.assets`; in development the endpoint runs a Vite watcher and points asset URLs at the Vite dev server so Svelte and CSS changes hot-reload with no extra terminal. The recommended way to install is via the [Igniter](https://github.com/ash-project/igniter) installer, which configures phoenix_vite (including the dev watcher and `static_url`) and all LiveSvelte steps.\n\n### Option A — Igniter installer (recommended)\n\nRequires Phoenix 1.8+ and Node.js 19+ (or Bun for package management and builds).\n\n#### New project\n\n```bash\nmix archive.install hex igniter_new\nmix igniter.new my_app --with phx.new --install live_svelte\ncd my_app\nmix setup\nmix phx.server\n```\n\n#### Existing project\n\nIgniter must be present in the project's deps before the installer can run.\n\n1. Add igniter to `mix.exs`:\n\n```elixir\ndefp deps do\n  [\n    # ... existing deps ...\n    {:igniter, \"~\u003e 0.6\"}\n  ]\nend\n```\n\n2. Fetch and run the installer (this adds `live_svelte`, configures [phoenix_vite](https://github.com/LostKobrakai/phoenix_vite), Vite, app.js, html_helpers, SSR, layout with `PhoenixVite.Components.assets`, and more):\n\n```bash\nmix deps.get\nmix igniter.install live_svelte\n```\n\n3. Install npm packages and build assets:\n\n```bash\nmix assets.setup     # phoenix_vite.npm assets install\nmix assets.build     # Vite client + SSR builds (or run mix setup to do both)\n```\n\n4. Start the server:\n\n```bash\nmix phx.server\n```\n\nWith phoenix_vite, the layout uses `PhoenixVite.Components.assets` and the endpoint uses `PhoenixVite.Plug`; no separate Vite terminal is required for dev — phoenix_vite integrates the Vite dev server.\n\nVisit `http://localhost:4000/svelte_demo` to confirm the demo Svelte component is working.\n\n#### Using Bun\n\nUse the `--bun` flag to use Bun instead of npm/npx:\n\n- **Existing project:** `mix igniter.install live_svelte --bun`\n- **New project:** `mix igniter.new my_app --with phx.new --install live_svelte --bun`\n\n---\n\n### Option B — Manual installation\n\nUse this if you prefer not to use Igniter, or need full control over the configuration.\n\nTo use **Bun** instead of npm when installing manually, add the `bun` dependency, set `config :phoenix_vite, PhoenixVite.Bun, ...`, and use `phoenix_vite.bun` in mix aliases and `PhoenixVite.Bun` in the Vite watcher. See the [Installation guide](https://hexdocs.pm/live_svelte/installation.html) for details.\n\n**1.** Add dependencies to `mix.exs`:\n\n```elixir\ndefp deps do\n  [\n    {:live_svelte, \"~\u003e 0.17\"}\n  ]\nend\n```\n\n**2.** Fetch deps:\n\n```bash\nmix deps.get\n```\n\n**3.** Add `import LiveSvelte` in `html_helpers/0` inside `lib/\u003capp_name\u003e_web.ex`:\n\n```elixir\ndefp html_helpers do\n  quote do\n    # ...\n    import LiveSvelte\n    # ...\n  end\nend\n```\n\n**4.** Add phoenix_vite and configure mix aliases (or use the Igniter installer which does this). With phoenix_vite:\n\n```elixir\n# mix.exs deps\n{:phoenix_vite, \"~\u003e 0.4\"}\n\n# config/config.exs\nconfig :phoenix_vite, PhoenixVite.Npm,\n  assets: [args: [], cd: __DIR__],\n  vite: [\n    args: ~w(exec -- vite),\n    cd: Path.expand(\"../assets\", __DIR__),\n    env: %{\"MIX_BUILD_PATH\" =\u003e Mix.Project.build_path()}\n  ]\n\n# mix.exs aliases\n\"assets.setup\": [\"phoenix_vite.npm assets install\"],\n\"assets.build\": [\n  \"phoenix_vite.npm vite build --manifest --emptyOutDir true\",\n  \"phoenix_vite.npm vite build --ssrManifest --emptyOutDir false --ssr js/server.js --outDir ../priv/svelte\"\n],\n\"assets.deploy\": [\"assets.build\", \"phx.digest\"]\n```\n\nUse `PhoenixVite.Components.assets` in your root layout and `import PhoenixVite.Plug` + `plug :favicon, dev_server: {PhoenixVite.Components, :has_vite_watcher?, [__MODULE__]}` in the endpoint. Without phoenix_vite, use `LiveSvelte.Reload.vite_assets` in the layout and run Vite manually.\n\n**For instant Svelte/CSS HMR**, add to your endpoint in `config/dev.exs`: `static_url: [host: \"localhost\", port: 5173]` and in `watchers` add `vite: {PhoenixVite.Npm, :run, [:vite, ~w(dev)]}`. The Igniter installer does this for you; if you add phoenix_vite or LiveSvelte manually, add these so the layout serves assets from the Vite dev server and HMR works.\n\n**5.** Update `assets/vite.config.mjs` to add the Svelte and LiveSvelte plugins:\n\n```js\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\"\nimport liveSveltePlugin from \"live_svelte/vitePlugin\"\n\n// Inside defineConfig plugins array:\nplugins: [\n  svelte({ compilerOptions: { css: \"injected\" } }),\n  liveSveltePlugin({ entrypoint: \"./js/server.js\" }),\n  // ... existing plugins\n]\n```\n\n**6.** Add `ssr: { noExternal: process.env.NODE_ENV === \"production\" ? true : undefined }` to the main `vite.config.mjs` so the same config is used for both client and SSR builds. The SSR build is run via `phoenix_vite.npm vite build --ssr js/server.js --outDir ../priv/svelte` (see aliases above). No separate `vite.ssr.config.js` is required when using phoenix_vite.\n\n**7.** Create `assets/js/server.js`:\n\n```js\nimport { getRender } from \"live_svelte\"\nimport Components from \"virtual:live-svelte-components\"\nexport const render = getRender(Components)\n```\n\n**8.** Update `assets/js/app.js` to wire in the LiveSvelte hooks:\n\n```js\nimport { getHooks } from \"live_svelte\"\nimport Components from \"virtual:live-svelte-components\"\n\n// Update your LiveSocket hooks:\nlet liveSocket = new LiveSocket(\"/live\", Socket, {\n  hooks: { ...getHooks(Components) },\n  // ...\n})\n```\n\n**9.** Update `assets/package.json` to add Svelte dependencies:\n\n```json\n{\n  \"dependencies\": {\n    \"live_svelte\": \"file:./deps/live_svelte\"\n  },\n  \"devDependencies\": {\n    \"svelte\": \"^5.0.0\",\n    \"@sveltejs/vite-plugin-svelte\": \"^5.0.0\"\n  }\n}\n```\n\n**10.** Add SSR configuration to `config/config.exs`, `config/dev.exs`, and `config/prod.exs`:\n\n```elixir\n# config/config.exs\nconfig :live_svelte, ssr: true\n\n# config/dev.exs\nconfig :live_svelte,\n  ssr_module: LiveSvelte.SSR.ViteJS,\n  vite_host: \"http://localhost:5173\"\n\n# config/prod.exs\nconfig :live_svelte,\n  ssr_module: LiveSvelte.SSR.NodeJS,\n  ssr: true\n```\n\n**11.** Add `NodeJS.Supervisor` to `lib/\u003capp_name\u003e/application.ex`:\n\n```elixir\nchildren = [\n  {NodeJS.Supervisor, [path: LiveSvelte.SSR.NodeJS.server_path(), pool_size: 4]},\n  # ... existing children\n]\n```\n\n**12.** For Tailwind support, add `@source \"../svelte\";` to `assets/css/app.css`.\n\n**13.** Install npm packages and build:\n\n```bash\nmix assets.setup\nmix assets.build\nmix phx.server\n```\n\n## Usage\n\nSvelte components need to go into the `assets/svelte` directory\n\nAttributes:\n\n-   `name`: Specify the Svelte component\n-   `props` _(Optional)_: Provide the `props` you want to use that should be reactive as a map to the props field\n-   `class` _(Optional)_: Provide `class` to set the class attribute on the root svelte element\n-   `ssr` _(Optional)_: Set `ssr` to `false` to disable server-side rendering\n\ne.g. If your component is named `assets/svelte/Example.svelte`:\n\n```elixir\ndef render(assigns) do\n  ~H\"\"\"\n  \u003c.svelte name=\"Example\" props={%{number: @number}} socket={@socket} /\u003e\n  \"\"\"\nend\n```\n\nIf your component is in a directory, for example `assets/svelte/some-directory/SomeComponent.svelte` you need to include the directory in your name: `some-directory/SomeComponent`.\n\n### The Components Macro\n\nThere is also an Elixir macro which checks your `assets/svelte` folder for any Svelte components, and injects local function `def`s for those components into the calling module.\n\nThis allows for an alternative, more JSX-like authoring experience inside Liveviews.\n\ne.g. in the below example, a Svelte component called `Example` is available to be called inside the Liveview template:\n\n```elixir\nuse LiveSvelte.Components\n\ndef render(assigns) do\n  ~H\"\"\"\n  \u003c.Example number={@number} socket={@socket} /\u003e\n  \"\"\"\nend\n```\n\n### Examples\n\nExamples can be found in the `/examples` and `/example_project` directories.\n\nMost of the `/example_project` examples are visible in the [YouTube demo video](https://www.youtube.com/watch?v=JMkvbW35QvA).\n\nI recommend cloning `live_svelte` and running the example project in `/example_project` by running the following commands:\n\n```bash\ngit clone https://github.com/woutdp/live_svelte.git\n# set up and run the example project\ncd live_svelte/example_project\nmix setup\nmix phx.server\n```\n\nThe example project uses phoenix_vite: `mix phx.server` starts both Phoenix and the Vite dev server, so Svelte/CSS changes hot-reload. Server will be running on `localhost:4000`. If you want to change the port, edit `example_project/config/dev.exs` and change the `http` config.\n\nIf you have examples you want to add, feel free to create a PR, I'd be happy to add them.\n\n#### Create a Svelte component\n\n```svelte\n\u003cscript\u003e\n    // The number prop is reactive,\n    // this means if the server assigns the number, it will update in the frontend\n    export let number = 1\n    // live contains all exported LiveView methods available to the frontend\n    export let live\n\n    function increase() {\n        // This pushes the event over the websocket\n        // The last parameter is optional. It's a callback for when the event is finished.\n        // You could for example set a loading state until the event is finished if it takes a longer time.\n        live.pushEvent(\"set_number\", {number: number + 1}, () =\u003e {})\n\n        // Note that we actually never set the number in the frontend!\n        // We ONLY push the event to the server.\n        // This is the E2E reactivity in action!\n        // The number will automatically be updated through the LiveView websocket\n    }\n\n    function decrease() {\n        live.pushEvent(\"set_number\", {number: number - 1}, () =\u003e {})\n    }\n\u003c/script\u003e\n\n\u003cp\u003eThe number is {number}\u003c/p\u003e\n\u003cbutton on:click={increase}\u003e+\u003c/button\u003e\n\u003cbutton on:click={decrease}\u003e-\u003c/button\u003e\n```\n\n_Note: that here we use the `pushEvent` function, but you could also use `phx-click` and `phx-value-number` if you wanted._\n\nThe following methods are available on `live`:\n\n-   `pushEvent`\n-   `pushEventTo`\n-   `handleEvent`\n-   `removeHandleEvent`\n-   `upload`\n-   `uploadTo`\n\nThese need to be run on the client, they can't be run in SSR. Either make sure they're called on an action (e.g. clicking a button) or wrap them with `onMount`.\n\nMore about this in the [LiveView documentation on js-interop](https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook)\n\n#### Create a LiveView\n\n```elixir\n# `/lib/app_web/live/live_svelte.ex`\ndefmodule AppWeb.SvelteLive do\n  use AppWeb, :live_view\n\n  def render(assigns) do\n    ~H\"\"\"\n    \u003c.svelte name=\"Example\" props={%{number: @number}} socket={@socket} /\u003e\n    \"\"\"\n  end\n\n  def handle_event(\"set_number\", %{\"number\" =\u003e number}, socket) do\n    {:noreply, assign(socket, :number, number)}\n  end\n\n  def mount(_params, _session, socket) do\n    {:ok, assign(socket, :number, 5)}\n  end\nend\n```\n\n```elixir\n# `/lib/app_web/router.ex`\nimport Phoenix.LiveView.Router\n\nscope \"/\", AppWeb do\n  ...\n  live \"/svelte\", SvelteLive\n  ...\nend\n```\n\n### LiveSvelte As An Alternative LiveView DSL\n\n[Blogpost on the topic](https://wout.space/notes/live-svelte-as-liveview-dsl)\n\nWe can go one step further and use LiveSvelte as an alternative to the standard LiveView DSL. This idea is inspired by [Surface UI](https://surface-ui.org/).\n\nTake a look at the following example:\n\n```elixir\ndefmodule ExampleWeb.LiveSigil do\n  use ExampleWeb, :live_view\n\n  def render(assigns) do\n    ~V\"\"\"\n    \u003cscript\u003e\n      export let number = 5\n      let other = 1\n\n      $: combined = other + number\n    \u003c/script\u003e\n\n    \u003cp\u003eThis is number: {number}\u003c/p\u003e\n    \u003cp\u003eThis is other: {other}\u003c/p\u003e\n    \u003cp\u003eThis is other + number: {combined}\u003c/p\u003e\n\n    \u003cbutton phx-click=\"increment\"\u003eIncrement\u003c/button\u003e\n    \u003cbutton on:click={() =\u003e other += 1}\u003eIncrement\u003c/button\u003e\n    \"\"\"\n  end\n\n  def mount(_params, _session, socket) do\n    {:ok, assign(socket, :number, 1)}\n  end\n\n  def handle_event(\"increment\", _value, socket) do\n    {:noreply, assign(socket, :number, socket.assigns.number + 1)}\n  end\nend\n```\n\nUse the `~V` sigil instead of `~H` and your LiveView will be Svelte instead of an HEEx template.\n\n#### Installation\n\n1. If it's not already imported inside `html_helpers/0`, add `import LiveSvelte` inside the `live_view` function in your project, this can be found in `/lib/\u003capp_name\u003e_web.ex`:\n\n```elixir\ndef live_view do\n  quote do\n    use Phoenix.LiveView,\n      layout: {ExampleWeb.Layouts, :app}\n\n    import LiveSvelte\n\n    unquote(html_helpers())\n  end\nend\n```\n\n2. Ignore build files in your `.gitignore`. The sigil will create Svelte files that are then picked up by Vite, these files don't need to be included in your git repo:\n\n```gitignore\n# Ignore automatically generated Svelte files by the ~V sigil\n/assets/svelte/_build/\n```\n\n#### Neovim Treesitter Config\n\nTo enable syntax highlighting in Neovim with Treesitter, create the following file:\n\n`~/.config/nvim/after/queries/elixir/injections.scm`\n\n```\n; extends\n\n; Svelte\n(sigil\n  (sigil_name) @_sigil_name\n  (quoted_content) @injection.content\n (#eq? @_sigil_name \"V\")\n (#set! injection.language \"svelte\"))\n```\n\nFor Neovim Treesitter version below v0.9:\n\n```\n; extends\n\n; Svelte\n(sigil\n  (sigil_name) @_sigil_name\n  (quoted_content) @svelte\n(#eq? @_sigil_name \"V\"))\n```\n\nAlso make sure Svelte and Elixir is installed in Treesitter.\n\n#### Options\n\nOptions can be passed in the mount by setting `svelte_opts`, check the following example:\n\n```elixir\ndef mount(_params, _session, socket) do\n  {:ok, assign(socket, some_value: 1, svelte_opts: %{ssr: false, class: \"example-class\"})}\nend\n```\n\n### LiveView Live Navigation Events\n\nInside Svelte you can define [Live Navigation](https://hexdocs.pm/phoenix_live_view/live-navigation.html) links. These links navigate from one LiveView to the other without refreshing the page.\n\nFor example this can be useful when you have a Svelte store and you want this store state to remain during navigation. Example of Svelte store usage can be found in `/examples/store`.\n\n`push_navigate`\n\n```svelte\n\u003ca href=\"/your-liveview-path\" data-phx-link=\"redirect\" data-phx-link-state=\"push\"\u003eRedirect\u003c/a\u003e\n```\n\n`push_patch`\n\n```svelte\n\u003ca href=\"/your-liveview-path\" data-phx-link=\"patch\" data-phx-link-state=\"push\"\u003ePatch\u003c/a\u003e\n```\n\n### LiveView JavaScript Interoperability\n\nLiveView allows for a bunch of interoperability which you can read more about here:\n\u003chttps://hexdocs.pm/phoenix_live_view/js-interop.html\u003e\n\n### Preprocessor\n\nTo use the preprocessor, install the desired preprocessor.\n\ne.g. Typescript\n\n```\ncd assets \u0026\u0026 npm install --save-dev typescript\n```\n\n### SSR (Server-Side Rendering)\n\nIf you're unfamiliar with SSR (Server-Side Rendering), it is a feature of Svelte to... render Svelte on the server. This means on first page load you get to see HTML instead of a blank page. Immediately after the first page load the page is '[hydrated](https://www.youtube.com/watch?v=D46aT3mx9LU)', which is a fancy word for adding reactivity to your component. This happens in the background, you don't notice this step happening.\n\nThe way LiveSvelte updates itself through LiveView is by letting Svelte handle all the HTML edits. Usually LiveView would edit the HTML by passing messages through the websocket. In our case we only pass the data we provided in the props attribute to Svelte through the websocket. No HTML is being touched by LiveView, Svelte takes care of that.\n\nLike mentioned, without SSR you'd see a brief flash of un-rendered content. Sometimes you can get away with not rendering Svelte on the server, for example when your Svelte component doesn't do any rendering on first page load and needs to be manually toggled for visibility by the user. Or when it is a component that has no visual component to it like tracking your mouse cursor and sending it back to the server.\n\nIn theses cases you can turn off SSR.\n\n**Important: Set NODE_ENV to production in production deployments**\n\nWhen deploying your application to production, you must set the `NODE_ENV` environment variable to `production`. If this is not done, your application may experience a memory leak and significantly reduced performance during SSR. For Docker deployments, you can add the following line to your production Dockerfile as an example:\n\n```\nENV NODE_ENV production\n```\n\n#### Disabling SSR\n\nSSR is enabled by default when you install LiveSvelte. If you don't want to use Server-Side Rendering for Svelte, you can disable it in the following ways:\n\n##### Globally\n\nIf you don't want to use SSR on any component you can disable it globally.\n\nThere are 2 ways of doing this\n\n-   Don't include the `NodeJS` supervisor in the `application.ex` file\n    or\n-   Add `ssr: false` to the `live_svelte` config in your `config.exs` file like so:\n\n```elixir\nconfig :live_svelte,\n  ssr: false\n```\n\n##### Component\n\nTo disable SSR on a specific component, set the `ssr` property to false. Like so:\n\n```\n\u003c.svelte name=\"Example\" ssr={false} /\u003e\n```\n\n### Auto-generated IDs and loops\n\nWhen the same Svelte component is rendered multiple times (e.g. in a `for` loop),\nLiveSvelte automatically generates unique, stable DOM IDs so that LiveView can\ncorrectly reconcile hook elements across re-renders.\n\n**How it works (priority order):**\n\n1. **Explicit `id`** — if you pass `id=\"my-id\"`, that value is used as-is.\n2. **Explicit `key`** — if you pass `key={index}`, the DOM id becomes `ComponentName-\u003ckey\u003e`.\n3. **Auto-detected identity from props** — LiveSvelte inspects the `props` map for\n   common identity keys (`:id`, `:key`, `:index`, `:idx` and their string equivalents).\n   When found, the value is appended to the component name to form a deterministic ID.\n4. **Counter fallback** — when none of the above apply, a simple per-name counter\n   produces sequential IDs (`Name`, `Name-1`, `Name-2`, …). This works for\n   standalone components but is **not** reliable inside comprehensions.\n\nFor most loops you already pass an `index` or `id` in your props, so IDs are\ngenerated automatically with no extra work:\n\n```elixir\n\u003c%= for {item, index} \u003c- Enum.with_index(@list) do %\u003e\n  \u003c%!-- index in props → auto-generates ids: \"Card-0\", \"Card-1\", … --%\u003e\n  \u003c.svelte name=\"Card\" props={%{index: index, color: @color}} /\u003e\n\u003c% end %\u003e\n```\n\nIf your props don't contain a natural identity key, use the `key` attribute:\n\n```elixir\n\u003c%= for {item, index} \u003c- Enum.with_index(@list) do %\u003e\n  \u003c.svelte name=\"Chart\" key={index} props={%{data: item.data}} /\u003e\n\u003c% end %\u003e\n```\n\n### Props diffing (change tracking)\n\nWhen props diffing is enabled, LiveSvelte sends only **changed** props to the client on updates, so unchanged props are not included in the payload and updates are minimal.\n\n-   **Config:** `config :live_svelte, enable_props_diff: true` (default). Set to `false` to always send full props.\n-   **Per component:** Use the `diff` attribute to opt out on a single component: `\u003c.svelte name=\"Example\" props={@props} diff={false} /\u003e`.\n\nOn the initial render or when the socket is disconnected, full props are always sent. When connected and diffing is on, only keys that changed (compared to the previous render) are sent, and the client merges them into existing state.\n\n### JSON Library\n\nLiveSvelte uses Erlang/OTP 27's native `:json` module by default for JSON encoding.\nThis provides excellent performance without requiring external dependencies.\n\n**Note:** LiveSvelte requires Elixir 1.17+ and OTP 27+ for the native JSON module.\n\n#### Using Jason or Poison\n\nIf you prefer to use Jason, Poison, or another JSON library, configure it in your `config.exs`:\n\n```elixir\n# config/config.exs\nconfig :live_svelte, json_library: Jason\n```\n\nAdd the dependency to your `mix.exs`:\n\n```elixir\n# mix.exs\ndefp deps do\n  [\n    {:live_svelte, \"~\u003e 0.17\"},\n    {:jason, \"~\u003e 1.2\"}  # or {:poison, \"~\u003e 5.0\"}\n  ]\nend\n```\n\nThe JSON library must implement `encode!/1` that accepts any Elixir term and returns a JSON string.\n\n#### Struct Encoding\n\nThe native JSON encoder automatically converts structs to maps before encoding. This means\nyou don't need `@derive Jason.Encoder` when using the default native JSON encoder.\n\nIf you're using Jason and need custom struct encoding behavior, see the\n[Structs and Ecto](#structs-and-ecto) section for details on `@derive Jason.Encoder`.\n\n#### LiveSvelte.Encoder protocol\n\nAll props pass through the `LiveSvelte.Encoder` protocol before JSON encoding. You can control which struct fields are sent to Svelte with `@derive`:\n\n```elixir\ndefmodule User do\n  @derive {LiveSvelte.Encoder, except: [:password]}\n  defstruct [:name, :email, :password]\nend\n```\n\n- `@derive LiveSvelte.Encoder` — encode all fields except `__struct__`\n- `@derive {LiveSvelte.Encoder, only: [:name, :email]}` — encode only listed keys\n- `@derive {LiveSvelte.Encoder, except: [:password]}` — encode all except listed keys\n\nPhoenix.HTML.Form, Ecto.Changeset, and Phoenix LiveView upload structs have built-in encoders. Date/Time types are encoded as ISO8601 strings.\n\nFor efficient updates when only some props change, use the built-in **props diffing** (see [Configuration](https://hexdocs.pm/live_svelte/configuration.html)) and JSON Patch support; the former `live_json` integration has been removed.\n\n### Structs and Ecto\n\nLiveSvelte serializes data passed in props to JSON so it can be handled by JavaScript.\n\n**With native JSON (default):** Structs are automatically converted to maps. No additional configuration needed.\n\n**With Jason:** Jason doesn't know how to handle structs by default, so you need to define it yourself using `@derive`.\n\n#### Structs (Jason only)\n\nIf you're using Jason and have a struct like this:\n\n```elixir\ndefmodule User do\n  defstruct name: \"John\", age: 27, address: \"Main St\"\nend\n```\n\nYou must define `@derive`\n\n```elixir\ndefmodule User do\n  @derive Jason.Encoder\n  defstruct name: \"John\", age: 27, address: \"Main St\"\nend\n```\n\nBe careful though, as you might accidentally leak certain fields you don't want the client to access, you can include which fields to serialize:\n\n```elixir\ndefmodule User do\n  @derive {Jason.Encoder, only: [:name, :age]}\n  defstruct name: \"John\", age: 27, address: \"Main St\"\nend\n```\n\n#### Ecto (Jason only)\n\nWhen using Jason with Ecto schemas, it's important to _also_ omit the `__meta__` field as it's not serializable.\n\nCheck out the following example:\n\n```elixir\ndefmodule Example.Planets.Planet do\n  use Ecto.Schema\n  import Ecto.Changeset\n  @derive {Jason.Encoder, except: [:__meta__]}\n\n  schema \"planets\" do\n    field :diameter, :integer\n    field :mass, :integer\n    field :name, :string\n\n    timestamps()\n  end\n\n  ...\nend\n```\n\n#### Documentation\n\nMore documentation on the topic:\n\n-   [HexDocs](https://hexdocs.pm/jason/Jason.Encoder.html)\n-   [GitHub](https://github.com/michalmuskala/jason#encoders)\n\n### Slots\n\nYou can slot Elixir inside a LiveSvelte component like so:\n\n```elixir\n\u003c.svelte name=\"Example\"\u003e\n  \u003cp\u003eSlot content\u003c/p\u003e\n\u003c/.svelte\u003e\n```\n\nAnd in the Svelte file it will look like this:\n\n```svelte\n\u003cscript\u003e\n    let {children}: = $props()\n\u003c/script\u003e\n\n\u003ci\u003eOpening\u003c/i\u003e\n  {@render children?.()}\n\u003ci\u003eClosing\u003c/i\u003e\n```\n\nNamed slots also work:\n\n```elixir\n\u003c.svelte name=\"Example\"\u003e\n  Main content\n  \u003c:subtitle\u003e\n    \u003cp\u003eSlot content\u003c/p\u003e\n  \u003c/:subtitle\u003e\n\u003c/.svelte\u003e\n```\n\n```svelte\n\u003cscript\u003e\n    let {children, subtitle}: = $props()\n\u003c/script\u003e\n\n\u003ci\u003eOpening\u003c/i\u003e\n  {@render children()}\n  \u003ch2\u003e{@render subtitle()}\u003c/h2\u003e\n\u003ci\u003eClosing\u003c/i\u003e\n```\n\nThis works because of the Snippet API provided by Svelte. Be careful though, it's a new feature that might not be working 100% of the time, I'd love to see what limitations you hit with it. One limitation is that you can't slot other Svelte components.\n\n\u003e ℹ️ **Note: Slotted Content is wrapped with a `div`**\n\u003e\n\u003e LiveSvelte wraps all slotted content in a `\u003cdiv\u003e` element.\n\u003e This is a limitation to how `createRawSnippet` works in Svelte, which is used under the hood in LiveSvelte to make this feature work.\n\n### Showing fallback content with :loading\n\nWhen SSR is disabled, a full‑page refresh briefly shows a blank content before Svelte finishes loading.\nUse the `:loading` slot to render a placeholder during that gap:\n\n```elixir\n\u003c.svelte name=\"Example\" ssr={false}\u003e\n  \u003c:loading\u003e\n    \u003cp\u003eUntil your LiveSvelte component renders client-side, this will be displayed\u003c/p\u003e\n  \u003c/:loading\u003e\n\u003c/.svelte\u003e\n```\n\n\n## Caveats\n\n### \"Secret State\"\n\nWith LiveView, it's easy to keep things secret. Let's say you have a conditional that only renders when something is `true`, in LiveView there's no way to know what that conditional will show until it is shown, that's because the HTML is sent over the wire.\n\nWith LiveSvelte, we're dealing with JSON being sent to Svelte, which in turn takes that JSON data and conditionally renders something, even if we don't set the conditional to `true`, the Svelte code will contain code on what to show when the conditional turns `true`.\n\nIn a lot of scenarios this is not an issue, but it can be and is something you should be aware of.\n\n## LiveSvelte Development\n\n### Local Setup\n\n#### Example Project\n\nYou can use `/example_project` as a way to test `live_svelte` locally.\n\n#### Custom Project\n\nYou can also use your own project.\n\nClone `live_svelte` to the parent directory of the project you want to test it in.\n\nInside `mix.exs`\n\n```elixir\n{:live_svelte, path: \"../live_svelte\"},\n```\n\nInside `assets/package.json`\n\n```javascript\n\"live_svelte\": \"file:../../live_svelte\",\n```\n\n### Development Workflow\n\nLiveSvelte ships TypeScript source directly — there are no pre-built distribution files. Consumers compile the library source through their own Vite pipeline via the `live_svelte` package alias.\n\nWhen working on the library source in `assets/js/live_svelte/`, rebuild the example project so Vite picks up the changes:\n\n```bash\n# From example_project/ — rebuild after library source changes:\nmix assets.build \u0026\u0026 mix compile\n```\n\nThe example project uses phoenix_vite, so a single `mix phx.server` starts both Phoenix and the Vite dev server; Svelte and library changes hot-reload with no second terminal.\n\n**Type checking:** The LiveSvelte client library is written in TypeScript with a type-safe public API (no `any` in exported types). Consumers get type hints via the package `types` entry. From the library repo, run `npm run typecheck` in `live_svelte/assets` to run `tsc --noEmit`.\n\n### Releasing\n\nFor hex.pm releases of LiveSvelte itself, use the `easy_publish`-powered tasks:\n\n```bash\nmix release.patch    # Patch version bump\nmix release.minor    # Minor version bump\nmix release.major    # Major version bump\n```\n\nThese commands will bump the version, update the changelog, commit, tag, push, and publish to Hex in one step.\n\n## Deployment\n\nDeploying a LiveSvelte app is the same as deploying a regular Phoenix app, except that you will need to ensure that `nodejs` (version 19 or later) is installed in your production environment.\n\nThe below guide shows how to deploy a LiveSvelte app to [Fly.io](https://fly.io/), but similar steps can be taken to deploy to other hosting providers.\nYou can find more information on how to deploy a Phoenix app [here](https://hexdocs.pm/phoenix/deployment.html).\n\n\u003e **Note:** For best performance and to avoid memory leaks, always set the `NODE_ENV` environment variable to `production` in your production environment.\n\n### Deploying on Fly.io\n\nThe following steps are needed to deploy to Fly.io. This guide assumes that you'll be using Fly Postgres as your database. Further guidance on how to deploy to Fly.io can be found [here](https://fly.io/docs/elixir/getting-started/).\n\n1. Generate a `Dockerfile`:\n\n```bash\nmix phx.gen.release --docker\n```\n\n2. Modify the generated `Dockerfile` to install `curl`, which is used to install `nodejs` (version 19 or greater), and also add a step to install our `npm` dependencies:\n\n```diff\n# ./Dockerfile\n\n...\n\n# install build dependencies\n- RUN apt-get update -y \u0026\u0026 apt-get install -y build-essential git \\\n+ RUN apt-get update -y \u0026\u0026 apt-get install -y build-essential git curl \\\n    \u0026\u0026 apt-get clean \u0026\u0026 rm -f /var/lib/apt/lists/*_*\n\n+ # install nodejs for build stage\n+ RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \u0026\u0026 apt-get install -y nodejs\n\n...\n\nCOPY assets assets\n\n+ # install all npm packages in assets directory\n+ WORKDIR /app/assets\n+ RUN npm install\n\n+ # change back to build dir\n+ WORKDIR /app\n\n...\n\n# start a new build stage so that the final image will only contain\n# the compiled release and other runtime necessities\nFROM ${RUNNER_IMAGE}\n\nRUN apt-get update -y \u0026\u0026 \\\n-  apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \\\n+  apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates curl \\\n   \u0026\u0026 apt-get clean \u0026\u0026 rm -f /var/lib/apt/lists/*_*\n\n+ # install nodejs for production environment\n+ RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \u0026\u0026 apt-get install -y nodejs\n\n...\n```\n\nNote: `nodejs` is installed BOTH in the build stage and in the final image. This is because we need `nodejs` to install our `npm` dependencies and also need it when running our app.\n\n3. Launch your app with the Fly.io CLI:\n\n```bash\nfly launch\n```\n\n4. When prompted to tweak settings, choose `y`:\n\n```bash\n? Do you want to tweak these settings before proceeding? (y/N) y\n```\n\nThis will launch a new window where you can tweak your launch settings. In the database section, choose `Fly Postgres` and enter a name for your database. You may also want to change your database to the development configuration to avoid extra costs. You can leave the rest of the settings as-is unless you want to change them.\n\nDeployment will continue once you hit confirm.\n\n5. Once the deployment completes, run the following command to see your deployed app!\n\n```bash\nfly apps open\n```\n\n## Svelte 4 -\u003e Svelte 5 migration guide\n\nSince version `0.15.0`, LiveSvelte supports Svelte 5. If you want to use Svelte 4, use version `0.14.0`. Note that Svelte 5 is backwards compatible with Svelte 4 for the most part, so even if you're using Svelte 4 syntax, with the latest version it should still work, and so there should be few reasons why to stay on version `0.14.0`.\n\nTo migrate your project from `0.14.0` to `0.15.0` you need to follow the following 3 steps:\n\n1. Update `mix.exs` and run `mix deps.get`\n\n```elixir\n# `mix.exs`\n{:live_svelte, \"0.15.0\"}`\n```\n\n2. Update to the latest Svelte 5 in your package.json\n\n```javascript\n  // package.json\n \"svelte\": \"^5\",\n```\n\n3. Update your Vite configuration to use `@sveltejs/vite-plugin-svelte` with Svelte 5.\nRefer to the installation guide for the current Vite setup.\n\n\n## Credits\n\n-   [Ryan Cooke](https://dev.to/debussyman) - [E2E Reactivity using Svelte with Phoenix LiveView](https://dev.to/debussyman/e2e-reactivity-using-svelte-with-phoenix-liveview-38mf)\n-   [Svonix](https://github.com/nikokozak/svonix)\n-   [Sveltex](https://github.com/virkillz/sveltex)\n\n## Alternatives for other frontend frameworks\n\n-   Vue: [LiveVue](https://github.com/Valian/live_vue)\n-   React: [LiveReact](https://github.com/mrdotb/live_react)\n\n## LiveSvelte Projects\n\n-   [ash-svelte-flowbite](https://github.com/dev-guy/ash-svelte-flowbite)\n-   [Local-First Todo App](https://github.com/tonydangblog/liveview-svelte-pwa)\n-   [Beacon LiveAdmin](https://github.com/BeaconCMS/beacon_live_admin)\n-   [TV Labs](https://tvlabs.ai/)\n\n_Using LiveSvelte in a public project? Let me know and I'll add it to this list!_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwoutdp%2Flive_svelte","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwoutdp%2Flive_svelte","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwoutdp%2Flive_svelte/lists"}