Changing iframe src in LiveView adds to the page history

Say you have this LiveView:

defmodule AppWeb.IframeLive do
  use Phoenix.LiveView

  def render(assigns) do
      <iframe id="wp" src="<%= @src %>"></iframe>
      <button phx-click="switch" phx-value-src="//">en</button>
      <button phx-click="switch" phx-value-src="//">ru</button>

  def mount(_params, _session, socket) do
    {:ok, assign(socket, src: "//")}

  def handle_event("switch", %{"src" => src}, socket) do
    {:noreply, assign(socket, :src, src)}

Every time you change the iframe source by clicking ‘en’ or ‘ru’, the current page url gets pushed to the history. Is there a way to avoid this?

Welcome @Proporus – this is not directly related to LiveView. In fact, there is currently an open issue for the HTML spec: whether to add a new mode for iframes where they don’t add to the joint session history.

Is there a way to avoid this?

Yes, I found two different methods to solve this problem and both will use a small client-side Javascript hook.

First, let’s modify the template:

<div id="wiki-frame" phx-hook="IframeSwitch" phx-update="ignore">
  <button data-src="//">en</button>
  <button data-src="//">ru</button>
  <iframe id="wp" src="//"></iframe>

A few things to note:

  • The wrapper div received a phx-hook and therefore it also received a required id.

  • The buttons lost their phx-click attribute.

  • Although not strictly necessary, for clarity the src attribute changed from phx-value-src to data-src.

Next, let’s setup IframeSwitch:

let IframeSwitch = {
  buttons() { return Array.from(this.el.querySelectorAll("button")); },
  iframe() { return this.el.querySelector("iframe"); },
  mounted() {
    this.buttons().forEach(btn => {
      btn.addEventListener("click", (e) => {
        // Invoke location.replace(src) on the iframe's window.
        // This approach will not update the joint session history,
        // however, it may not work for all sources.

        // Remove the iframe from the DOM, set the src attribute, and re-add it.
        // This will not update the joint session history.
        // Requires a phx-update=ignore wrapper element.
        // let iframe = this.iframe();
        // iframe.remove();
        // iframe.setAttribute("src",;
        // this.el.append(iframe);

        // BROKEN: Note, this version exhibits the bad behaviour of updating
        // the joint session history. I am including it here to show that this
        // problem is not specific to LiveView.
        // this.iframe().src =;

You could also check out Alpine.js and apply the same concept using some lightweight, inline expressions.


Thanks in advance to anyone who replies with an Alpine.js version of IframeSwitch! That’d be neat to see.

1 Like

AlpineJS version:

<div id="wiki-frame" phx-update="ignore" x-data="{src: '//'}">
  <button @click="$refs.iframe.contentWindow.location.replace('//')">en</button>
  <button @click="$refs.iframe.contentWindow.location.replace('//')">ru</button>
  <iframe id="wp" :src="src" x-ref="iframe" />