Search code examples
htmlreactjsphoenix-frameworkhtml-renderinghtml-escape

React/Phoenix - escaping HTML will briefly flash unrendered HTML in browser


I'm running a React/Phoenix app that uses server-side rendering, and I'm finding that when I refresh the app rapidly, I can for a split second see the unrendered HTML from the server in the browser before it all renders correctly. This only happens to HTML that is escaped, but I want to use escaped HTML for this app as I don't want the browser to assume that it is safe. This happens in Chrome and Firefox, but not in Safari.

The app was scaffolded using RePh (which I highly recommend) - https://github.com/reph-stack/reph - and the relevant controller is:

defmodule MyAppWeb.ReactController do
  use MyAppWeb, :controller

  def index(conn, _params) do
    initial_state = %{}
    react_stdio_args = %{
      component: Application.app_dir(:my_app, "priv/static/server/js/app.js"),
      props: %{
        "location" => conn.request_path,
        "initial_state" => initial_state
      }
    }
    {:ok, %{"html" => html}} = StdJsonIo.json_call(react_stdio_args)
    render(conn, "index.html", html: html, initial_state: initial_state)
  end
end

which pipes data to index.html.eex:

<div id="index"><%= @html %></div>
<script>__INITIAL_STATE__=<%= @initial_state |> Poison.encode! |> raw %></script>

In the default setup for this scaffold, the @html is marked as safe with raw - <%= raw @html %> - but as you can see, I've removed that.

So anyway, if I refresh the browser window at a 'reasonable' pace, there's no problem... but if I start refreshing quickly, I can briefly see the unrendered HTML in the browser window, something like:

<div class="AppContainer wrapper" data-reactroot="" data-reactid="1" data-react-checksum="-1859726426"><header class="site-header" data-reactid="2"> etc...

Needless, to say, this does not happen if I leave raw in. I've tried some other escaping methods as well, such as Phoenix.HTML.html_escape(html) in the controller, but the same problem occurs.

I imagine that this happens because the browser needs that tiny sliver of a second to properly parse and render the escaped HTML, and the differences in browser performance can be chalked up to the different layout engines (perhaps?)... but I'm not an expert in these matters, so I don't know for sure. Is there any way that I can force my app to wait until the HTML is properly rendered before displaying it for the user?


Solution

  • @html here is a string of HTML. You cannot make it "safer" by removing raw. Removing raw will escape all the < and > in the string making the browser show the raw HTML code. You need to use raw here for SSR to work correctly. You can see this behavior if you try the following:

    <%= raw "<strong>Raw</strong>" %>
    <%= "<strong>Escaped</strong>" %>