<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brett Richardson</title>
    <description>The latest articles on DEV Community by Brett Richardson (@devbrett-dot-com).</description>
    <link>https://dev.to/devbrett-dot-com</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3614564%2F9c7fd9b1-01a0-4323-874b-5032849e41d8.jpg</url>
      <title>DEV Community: Brett Richardson</title>
      <link>https://dev.to/devbrett-dot-com</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devbrett-dot-com"/>
    <language>en</language>
    <item>
      <title>When Phoenix LiveView is the wrong choice</title>
      <dc:creator>Brett Richardson</dc:creator>
      <pubDate>Mon, 17 Nov 2025 04:41:23 +0000</pubDate>
      <link>https://dev.to/devbrett-dot-com/choosing-phoenix-liveview-1o88</link>
      <guid>https://dev.to/devbrett-dot-com/choosing-phoenix-liveview-1o88</guid>
      <description>&lt;p&gt;If I need to ship a new product in a week, &lt;a href="https://phoenixframework.org/" rel="noopener noreferrer"&gt;Phoenix LiveView&lt;/a&gt; is my go-to framework.&lt;/p&gt;

&lt;p&gt;Elixir LiveView is incredible, and an alluring choice for software leaders looking to develop applications super fast. However, in recent experience, I've seen teams hit some pitfalls.&lt;/p&gt;

&lt;p&gt;The trick is to understand what Elixir LiveView excels at, and what it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: LiveView is perfect for internal tools and simple apps. Skip it for complex UIs, offline-first apps, or if your team doesn't know Elixir well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;# This &lt;span class="k"&gt;is&lt;/span&gt; what &lt;span class="k"&gt;a&lt;/span&gt; very simple LiveView component looks like&lt;span class="p"&gt;.&lt;/span&gt;

defmodule DemoLive &lt;span class="k"&gt;do&lt;/span&gt;
  use Phoenix&lt;span class="p"&gt;.&lt;/span&gt;LiveView

  &lt;span class="k"&gt;def&lt;/span&gt; render&lt;span class="p"&gt;(&lt;/span&gt;assigns&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;~&lt;/span&gt;H&lt;span class="s2"&gt;""&lt;/span&gt;"
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;div&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;div&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Counter&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;@counter&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;button phx&lt;span class="p"&gt;-&lt;/span&gt;click&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"inc"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;+&amp;lt;&lt;/span&gt;/button&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;/div&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;MySortComponent&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt; 
        lists&lt;span class="p"&gt;={[&lt;/span&gt;first_list&lt;span class="p"&gt;:&lt;/span&gt; @first_list&lt;span class="p"&gt;,&lt;/span&gt; second_list&lt;span class="p"&gt;:&lt;/span&gt; @second_list&lt;span class="p"&gt;]}&lt;/span&gt; 
      /&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;/div&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;"""&lt;/span&gt;
  end

  &lt;span class="k"&gt;def&lt;/span&gt; handle_event&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"inc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; _&lt;span class="p"&gt;,&lt;/span&gt; socket&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{:&lt;/span&gt;noreply&lt;span class="p"&gt;,&lt;/span&gt; assign&lt;span class="p"&gt;(&lt;/span&gt;socket&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;counter&lt;span class="p"&gt;,&lt;/span&gt; socket&lt;span class="p"&gt;.&lt;/span&gt;assigns&lt;span class="p"&gt;.&lt;/span&gt;counter &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Good
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Avoid Javascript and Typescript
&lt;/h3&gt;

&lt;p&gt;Skip the Typescript or Javascript. Avoid the monthly &lt;code&gt;npm&lt;/code&gt; meltdowns and security nightmares. You can almost write your entire application in Elixir, and Phoenix LiveView will handle the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid "Hand Off" Between Frontend and Backend
&lt;/h3&gt;

&lt;p&gt;The mental model of a LiveView component is incredibly simple.&lt;/p&gt;

&lt;p&gt;How long does your engineering team spend writing &lt;a href="https://hex.pm/packages/open_api_spex" rel="noopener noreferrer"&gt;OpenAPISpex&lt;/a&gt;, carefully crafting the API contract between your frontend and backend? With LiveView, you don't have to; there is no "hand off" between frontend and backend. No argument or discussion about what the JSON payload should look like, no "glue code" to write. There's no ALT-TAB to a different IDE when you push a new endpoint. &lt;strong&gt;It's just there&lt;/strong&gt;, in one &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-templates" rel="noopener noreferrer"&gt;.heex&lt;/a&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  UI Feels Super Responsive
&lt;/h3&gt;

&lt;p&gt;It's fast. It feels snappy and responsive, in a way heavy React or Vue applications often don't. Most of the time, your browser is rendering static HTML, so it feels as snappy as a static HTML website (like this one). There's no skeleton loading states, no flickering, no re-rendering of the entire page when you click a button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy to Test
&lt;/h3&gt;

&lt;p&gt;You can test your LiveView components in isolation, without having to spin up a full web server. You get what would normally be considered "E2E tests" without the hassle of orchestrating a full test environment. No juggling Selenium servers and no paying for Cypress licenses.&lt;/p&gt;

&lt;h3&gt;
  
  
  It Handles "Multiplayer" Interfaces &lt;strong&gt;Really&lt;/strong&gt; Well
&lt;/h3&gt;

&lt;p&gt;LiveView is a great fit for building "multiplayer" interfaces. You can build a chat application, a real-time collaborative document, or a real-time multiplayer game with ease. Because it's built on top of WebSockets, it's incredibly easy to build these types of applications, and the Phoenix PubSub system is built to handle the complexity of scaling to thousands of concurrent users.&lt;/p&gt;

&lt;h3&gt;
  
  
  It Handles "Moment-to-Moment" Interfaces &lt;strong&gt;Really&lt;/strong&gt; Well
&lt;/h3&gt;

&lt;p&gt;If you are trying to build an application where the backend is going to be regularly pushing data to the frontend frequently, LiveView is a great fit. Anything like a realtime dashboard, live-updating graphs and charts, or any sort of moment-to-moment interface is a great fit for LiveView. This is why many Elixir developers use LiveView to build realtime dashboards and admin interfaces.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bad
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complex Custom UI Elements
&lt;/h3&gt;

&lt;p&gt;If you need something more complex than a simple button and some HTML forms, I'll be honest, now is when you'll have to pick a side. Performing complex UI interactions like...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data tables with sorting, filtering, and pagination&lt;/li&gt;
&lt;li&gt;Chat windows&lt;/li&gt;
&lt;li&gt;Drag and drop interfaces&lt;/li&gt;
&lt;li&gt;Multiple nested modals&lt;/li&gt;
&lt;li&gt;Interactive graphs&lt;/li&gt;
&lt;li&gt;Interactive maps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...are all possible, but this is &lt;strong&gt;not&lt;/strong&gt; what LiveView excels at.&lt;/p&gt;

&lt;p&gt;You'll find yourself wrestling with complex "bridging code" and you'll probably lose out on the benefits of LiveView.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevbrett.com%2F2025%2F11%2Fchoosing-phoenix-liveview%2Fimages%2Fdisconnected.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevbrett.com%2F2025%2F11%2Fchoosing-phoenix-liveview%2Fimages%2Fdisconnected.png" alt="Disconnected LiveView" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  LiveView Struggles with Network Instability
&lt;/h3&gt;

&lt;p&gt;If you expect your users to have patchy internet connections, you'll find LiveView struggles to handle disconnects gracefully. If you expect your users to be out-and-about while using your product, it's unlikely LiveView will be a good fit. Any sort of navigation or mapping application is likely to be a poor experience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# You'll want to ensure you have sticky sessions enabled on your load balancer.&lt;/span&gt;

&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;phoenix_backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;ip_hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# Routes same IP to same server&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;10.0.1.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;10.0.1.2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;10.0.1.3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DevOps and Infrastructure Can Be More Complex
&lt;/h3&gt;

&lt;p&gt;You'll probably need to ensure you have sticky sessions enabled if you're load balancing your application. This means reconnects are directed to the same server instance, rather than a new one. This is a simple configuration change, but it's one that you'll need to be aware of.&lt;/p&gt;

&lt;p&gt;The way LiveView handles sessions and authenticating sockets is not immediately obvious, so you'll need to be aware and ensure you have a good understanding of the underlying mechanisms.&lt;/p&gt;

&lt;p&gt;In addition, many reverse proxies like Nginx will not support WebSockets without additional configuration, and long-lived connections may get terminated after a short timeout by default. Websockets are also a little more difficult to debug than traditional HTTP requests, so you'll need to be aware and have good backend logging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# You'll want to ensure you have sticky sessions enabled on your load balancer.&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"phoenix"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;stickiness&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lb_cookie"&lt;/span&gt;
    &lt;span class="nx"&gt;cookie_duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Third-party Component Support
&lt;/h3&gt;

&lt;p&gt;Third-party support is still lacking, so if you need a really nice calendar UI for example, your choices are limited. You'll probably find &lt;strong&gt;something&lt;/strong&gt; but it's unlikely to be a perfect fit. If you're willing to make-do and sacrifice some features, you'll probably be fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  You're Building a Complex Application Anyway
&lt;/h3&gt;

&lt;p&gt;If you only need a few custom UI elements, you'll probably be fine, as long as they're not going to be deeply nested. If you carefully choose a small number of key UI components that are &lt;strong&gt;core to your product experience&lt;/strong&gt;, it might still be worth it.&lt;/p&gt;

&lt;p&gt;Complex custom UI elements will require you to pick one of the following trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WebComponents without a framework&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can develop web components without a framework, but you'll need to write a lot of boilerplate to get what React or Vue gives you out of the box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloCounter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
        &amp;lt;span&amp;gt;Hello world! Count: &amp;lt;span id="count"&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;button id="inc"&amp;gt;Increment&amp;lt;/button&amp;gt;
      `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_countEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_buttonEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#inc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onClick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_buttonEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onClick&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;disconnectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_buttonEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onClick&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_onClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_countEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello-counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HelloCounter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WebComponents with a framework&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I recommend the fantastic &lt;a href="https://lit.dev/" rel="noopener noreferrer"&gt;Lit&lt;/a&gt; framework from Google. It's a great way to develop web components with a framework, and it's easy to learn.&lt;br&gt;
  The trade-off is that you'll be writing Typescript, and nesting components is very complex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;LitElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lit/decorators.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyElement&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STARTING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;p&amp;gt;Welcome to the Lit tutorial!&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;This is the &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; code.&amp;lt;/p&amp;gt;
      `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React or Vue&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can render React components directly in your LiveView using the &lt;code&gt;phx-hook&lt;/code&gt; attribute. Again, you'll be writing Typescript, and if you want to benefit from local state management, you might find yourself wrestling with the data model of LiveView, unsure where to store the state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;div id&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"react-root"&lt;/span&gt;
      phx&lt;span class="p"&gt;-&lt;/span&gt;hook&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyCustomReactWidget"&lt;/span&gt;
      data&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;={&lt;/span&gt;@&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      phx&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;update&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ignore"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;/div&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Ugly
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Learning Curve; LiveView is Foreign to Most
&lt;/h3&gt;

&lt;p&gt;LiveView is relatively new, and it operates in a way that will be unfamiliar to most frontend developers.&lt;/p&gt;

&lt;p&gt;In fact, you're likely to find backend specialists are more comfortable with the framework. LiveView uses Elixir and OTP's GenServers as a primitive building block, and without strong understanding of how OTP processes, message passing, and concurrency works, you'll find yourself struggling to grok the LiveView concepts built on top of them.&lt;/p&gt;

&lt;p&gt;Even worse, Elixir itself is one of the most complex programming languages to master. While the syntax is simple and easy to initially pick up, the BEAM VM and the ecosystem are highly complex and quite different to anything else in the programming world.  There are common programming patterns like the "early return", "inheritance", and "polymorphism" that are not strictly supported by Elixir, forcing developers to internalise completely different paradigms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hiring Can Be Tough
&lt;/h3&gt;

&lt;p&gt;Elixir is still a niche tool. It's not as popular as Python, JavaScript, or even Ruby so you'll have to look harder to find experienced Elixir developers, especially in Australia! Adoption is rapidly growing, not least due to LiveView itself and Elixir's unique strength for orchestrating AI agents and AI workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  However... Elixir Developers Are Highly Experienced
&lt;/h3&gt;

&lt;p&gt;That said, one redeeming quality of Elixir, is that when you find someone experienced with the language, they tend to be more knowledgeable and senior than other languages. It is &lt;strong&gt;very rare&lt;/strong&gt; for a junior developer to choose Elixir as their first language, and most of its denizens are highly experienced engineers who have learnt Elixir as an extension of strong pre-existing skills.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevbrett.com%2F2025%2F11%2Fchoosing-phoenix-liveview%2Fimages%2Fgoogle_trends.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevbrett.com%2F2025%2F11%2Fchoosing-phoenix-liveview%2Fimages%2Fgoogle_trends.png" alt="Google Trends" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

Google Trends shows LiveView has increased interest recently, but still trails majorly behind React, Vue and even Web Components.




&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;LiveView makes your developers many-times more productive than traditional web frameworks. However, this comes with a few caveats. You will not be able to use LiveView for every application you build. Be selective in your choices, and consider the following:&lt;/p&gt;

&lt;h3&gt;
  
  
  Where LiveView Shines: Internal Tooling
&lt;/h3&gt;

&lt;p&gt;If you are already using Elixir and Phoenix, then using LiveView &lt;strong&gt;for internal tooling&lt;/strong&gt; is a no-brainer. Using LiveView for simple admin interfaces and internal tools is a great way to get started with the framework.&lt;/p&gt;

&lt;p&gt;Most of the downsides to adopting LiveView do not apply to internal tooling. You don't need to worry about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product arguing that customers &lt;strong&gt;need&lt;/strong&gt; that fancy UI element&lt;/li&gt;
&lt;li&gt;Network instability&lt;/li&gt;
&lt;li&gt;Complex custom UI&lt;/li&gt;
&lt;li&gt;Performance or scalability issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many of Elixir and Phoenix's community admin tools are built with LiveView, so you'll be able to find a lot of inspiration and examples for your own projects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://livebook.dev/" rel="noopener noreferrer"&gt;LiveBook&lt;/a&gt;: A Jupyter-like notebook environment for Elixir built with LiveView&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ash-hq.org/" rel="noopener noreferrer"&gt;Ash Admin&lt;/a&gt;: A Powerful Admin Interface for Phoenix LiveView&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://oban.pro" rel="noopener noreferrer"&gt;Oban Pro&lt;/a&gt;: A Powerful Job Queue for Phoenix LiveView&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/phoenixframework/phoenix_live_dashboard" rel="noopener noreferrer"&gt;Phoenix Live Dashboard&lt;/a&gt;: A Powerful Dashboard for Phoenix Applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Where LiveView Shines: Simple Customer-Facing Applications
&lt;/h4&gt;

&lt;p&gt;If you're building a simple customer-facing application, and want to optimise for time-to-market, you'll probably love LiveView, assuming your team already knows Elixir!&lt;/p&gt;

&lt;p&gt;I have frequently been able to deliver entire applications in just a few days.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consider
&lt;/h3&gt;

&lt;p&gt;If you're not sure whether LiveView is a good fit for your application, here are a few things to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you need to support older browsers?&lt;/li&gt;
&lt;li&gt;Will your company's existing infrastructure be able to support very long-lived WebSocket connections?&lt;/li&gt;
&lt;li&gt;Is your application going to be a "multiplayer" interface?&lt;/li&gt;
&lt;li&gt;Is your application going to be a "moment-to-moment" interface?&lt;/li&gt;
&lt;li&gt;Do you need to support complex custom UI elements?&lt;/li&gt;
&lt;li&gt;Is your team familiar with Elixir?&lt;/li&gt;
&lt;li&gt;Do you need to handle network instability?&lt;/li&gt;
&lt;li&gt;Do you need to maintain complex local state in the browser?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Get in Touch
&lt;/h2&gt;

&lt;p&gt;If you have any questions or would like to discuss your project, please get in touch.&lt;br&gt;
I have extensive experience with both traditional web frameworks like React and Vue, and LiveView, and would be happy to discuss your project and whether LiveView is a good fit for you.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
      <category>liveview</category>
    </item>
  </channel>
</rss>
