Juneum
Elixir, Phoenix, and Javascript

Static Assets with Phoenix LiveView

June 6, 2021
trisager

Phoenix LiveView is a tool that lets you create interactive user interfaces. Let's say your application has a page showing a list of customers that the user can filter by typing a string into a search field. Instead of requiring the user to finish typing and pressing a "submit" button before showing the filtered list, with LiveView you can send updates from the server to the browser dynamically, as the user types.

LiveView accomplishes this by establishing a persistent websocket connection between the browser and the server when the initial page rendering has been completed. Changes to the search input are sent to the server over this websocket, and updates to the page come back in the opposite direction. The updates are then applied to the client DOM by JavaScript, so only the parts of the page that are affected by the changes are updated. This is faster and requires less bandwidth than having the server send a complete updated page in response to a POST request.

One disadvantage of this approach is that static assets such as css and JavaScript are only refreshed when the entire page is reloaded, and this may not happen very often. If you release a new version of your application with css or JavaScript changes, you may have to wait a while until all connected users get the updates (e.g. when they restart their browser or device).

Detecting Asset Changes

LiveView provides a mechanism that lets the server detect if a client is using outdated static assets. If you generate a Phoenix application using mix phx.new with the --live option, the root.html.leex template will contain:

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <link phx-track-static rel="stylesheet" 
      href="<%= Routes.static_path(@conn, "/css/app.css") %>"
    />
    <script defer phx-track-static type="text/javascript" 
      src="<%= Routes.static_path(@conn, "/js/app.js") %>">
    </script>
  </head>
  ...
</html>

Note the phx-track-static attributes on the stylesheet link and the JavaScript.

When you build a new release for deployment, you use mix phx.digest to create "digested" versions of your static files, i.e files with names that contain a checksum calculated on the file content. E.g:

  • app.css becomes app-e2180101a01bef248375261eb58b6c55.css
  • app.js becomes app-9d8253583c5c2c6ae856b59e0624aee7.js

When the LiveView client creates the websocket connection to the server, it will include the names of the tracked assets in the phx-join message:

...
params: {
  _track_static: [
   "https://example.com/css/app-e2180101a01bef248375261eb58b6c55.css?vsn=d",
   "https://example.com/js/app-9d8253583c5c2c6ae856b59e0624aee7.js?vsn=d"
  ]
}
...

LiveView provides a static_changed?/1 function that returns true if the tracked static assets on the server are different from the ones the client is using. You can make the value of that function available to your view on mount:

def mount(params, session, socket) do
  {:ok, assign(socket, static_changed?: static_changed?(socket))}
end

You can check the value of @static_changed in your LiveView template and perform some action if it is true. E.g. (from the documentation):

<%= if @static_changed? do %>
  <div id="reload-static">
    The app has been updated. 
    Click here to <a href="#" onclick="window.location.reload()">reload</a>.
  </div>
<% end %>

This helps avoid situations where long-running LiveView clients end up using an outdated version of your application.

← Back to articles