How include layouts with render_to_iodata

I am trying to cache static web pages in ETS, and using render_to_iodata does not seem to include the layout. I was wondering the proper way to do this. Here is how I am currently using it in my controller:

def post(conn, _param) do
  rendered_page = Phoenix.View.render_to_iodata(BlogWeb.LayoutView, "app.html", 
        conn: conn,
        view_module: BlogWeb.PostView,
        view_template: "post.html"
  html(conn, rendered_page)

What happens is that this spins forever and nothing gets loaded, and nothing is really logged to the console.

My page.html.eex:

Hello, world!

And the layout/app.html.eex (shortened) is:

<%= render @view_module, @view_template, assigns %>

I have a barebones view for each. I am using the default layout.

Since you’re not getting any errors it’s hard to say what’s wrong. I’ve never tried to do it that way, but I can suggest an alternative solution. You can cache rendered content by using Plug.Conn.register_before_send/2. Here’s some untested code

def post(conn, _param) do
  |> Plug.Conn.register_before_send(cache/1)
  |> render("post.html")

def cache(conn) do
  content = :erlang.iolist_to_binary(conn.resp_body)
  :ets.insert(:table, {you_pick_your_key, content})

The reason you want to coerce the iodata to binary is that based on experience the ETS memory usage will balloon if you don’t.

This solution also means you can create a Plug responsible for checking the cache and caching responses, and add it to a pipeline. I have an example of that here

Note that the example is specific to my use case, you’d want to adjust it to yours :slight_smile:


This seems much more idomatic. Thank you very much!