Heading image for post: Error Page on Phoenix LiveView

Elixir Phoenix

Error Page on Phoenix LiveView

Profile picture of Vinicius Negrisolo

It's been a couple of years that I don't build error pages in Elixir, and the last time I did there was no LiveView in the app. There are a few things that changed since then so here in this post we'll cover what we need to know to build those error pages.

First of all, the whole idea of the Error Page is to present to an user an ok page for when something not expected happens, such as a 404 - Not Found page. Ideally you should have no link that drives the user to a 404 error. Still, that happens. Even if your app is a bug-free app, which I doubt it, errors will happen eventually. The premise here is to create a simple page, with very minimal dependencies just to show a message to the user that we'll be working on this error. We want to guarantee that rendering the error page itself will not generate another error, no recursive errors :/

The work needed to implement a simple page is very minimal so there should be no excuse not to implement them, even if that happens only a few times per year. So let's start it!

Raising errors in dev mode

To start we need to change a config/dev.exs to tell our endpoint that we want to show the real error page, not the Phoenix created error pages for debug. To do that apply this change:

# config/dev.exs
config :my_app, MyAppWeb.Endpoint,
  #...
-  debug_errors: true,
+  debug_errors: false,
  #...

Now if you run the server and try to get a http://localhost:4000/some/wrong/path that would show a blank page with the text Not Found.

HTML templates

Phoenix uses a plug protocol called Plug.Exception to infer what's going to be the HTTP response code per Elixir Exception that was raised. In case you want to create your own Custom Exceptions go ahead and do that, it's simple and it's nice.

On our case we'll just use the 404 erros from non mapped routes to test the HTML template. If you look for our Phoenix Endpoint config.exs you'll see that we have a render_errors: config that points to a module MyAppWeb.ErrorHTML. Go to that module and add this line:

defmodule MyAPpWeb.ErrorHTML do
  use MyAPpWeb, :html

+  embed_templates "error_html/*"

  #...
end

Now create this file: lib/my_app_web/controllers/error_html/404.html.heex:

<!-- lib/my_app_web/controllers/error_html/404.html.heex -->
<p>
  Oops, The page you are looking for cannot be found.
</p>
<p>
  Our team was notified and is working on that. Please go
  <.link href={~p"/"}>
    back to the site
  </.link>
</p>

A couple caveats with the template

First of all, you do have access to the assigns, so let's say that your error happened after you set your current_user then you could use that in our error message if you want, just like any heex variable: <%= @current_user.email %>, but remember that depends on the error that could have happened before that set_current_user call. And remember that we do want a zero chance of an error in the error page. On this example plug will check the routes before executing any plug. If they hit http://localhost:4000/some/wrong/path then plug will halt that request before setting the current_user in the assigns.

Secondly you'll notice that none of your css and js will be available on those error pages. This was intentionally to minimize the chance of errors (again) and this was done by setting the root_layout and the layout as false. So if you want, let's say bring tailwind to the error pages so you can do some basic styling you will have to define a root_layout for those error pages.

Adding a root layout

So go ahead and create a root layout for your errors on lib/my_app_web/controllers/error_html/root.html.heex:

<!-- lib/my_app_web/controllers/error_html/root.html.heex -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="csrf-token" content={get_csrf_token()} />
    <title>My App ยท Error</title>
    <link phx-track-static rel="stylesheet" href={~p"/assets/tailwind.css"} />
  </head>
  <body>
    <%= @inner_content %>
  </body>
</html>

And let's change the config/config.exs file:

config :my_app, MyAPpWeb.Endpoint,
  url: [host: "localhost"],
  render_errors: [
    formats: [html: MyAPpWeb.ErrorHTML, json: MyAPpWeb.ErrorJSON],
+    root_layout: {MyAPpWeb.ErrorHTML, :root},
    layout: false
  ],

Now we can use tailwind on each of those error pages.

Default to 500 error

For now we did not create any custom Exception mapping to http codes yet, and we are only mapping a 404 and a 500 errors. And I did a small change to render the template for the 500 error as the default by doing this change to the ErrorHTML module:

defmodule MyAppWeb.ErrorHTML do
  use MyAppWeb, :html

  embed_templates "error_html/*"

  def render(template, assigns) do
-    Phoenix.Controller.status_message_from_template(template)
+    apply(__MODULE__, :"500", [assigns])
  end
end

So in case a dev forget to create a specific error page for a new http code then we are covered here.

Just before commiting this don't forget to change the dev mode back to the default phoenix debug error pages as they are very useful:

# config/dev.exs
config :my_app, MyAppWeb.Endpoint,
  #...
+  debug_errors: true,
-  debug_errors: false,
  #...

That's all that I have for today, thanks for reading!

More posts about Elixir Phoenix error page

  • Adobe logo
  • Barnes and noble logo
  • Aetna logo
  • Vanderbilt university logo
  • Ericsson logo

We're proud to have launched hundreds of products for clients such as LensRentals.com, Engine Yard, Verisign, ParkWhiz, and Regions Bank, to name a few.

Let's talk about your project