How do you update a page's title, meta description, open graph and other head tags when using Turbo Frames with "advance"?

Hi,

With turbo-rails 7.3.0 (latest version at the time of this post):

Let’s say you use “advance” mode with Turbo frames which allows you to update a URL as you navigate between frames. This is very handy in a number of use cases such as having a sidebar with a main content area. Each sidebar link could render a new frame in the main content area to avoid rendering an expensive sidebar.

I ran into this exact use case with a video platform where I want to use frames to avoid reloading an <iframe> to have a permanent video that doesn’t lose its playback position and to also avoid rendering an expensive query to render a table of contents. Turbo frames feels like it was designed for this exact purpose!

The problem with the above is Turbo frames only swaps the frame (by nature) but when navigating between URLs you would expect the following <head> tags to be updated:

  • Title
  • Meta description
  • Canonical URL
  • Open Graph tags
  • Potentially more <head> tags

There’s an open issue for this at Easy way to update <title> and other elements using Frames · Issue #600 · hotwired/turbo · GitHub from about a year ago but it hasn’t been picked up. In my opinion this is a pretty big deal, I’m wondering if I’m missing something obvious on how to get these attributes to update in a maintainable way while using frames.

Not having these updates is kind of a deal breaker for using Turbo frames with “advance” where you change the URL since having those <head> fields not get updated could penalize you from Google and other crawlers. It also makes your site appear to be buggy. For example here’s an issue from the exercism.io site which uses frames in this way but doesn’t get updated properly Browser tab shows wrong title · Issue #6636 · exercism/exercism · GitHub.

Any thoughts or suggestions on how to handle this in a clean way?

Thanks!

4 Likes

I think you’d want to hook into the new minimal layout and use content_for blocks but I haven’t tried this myself. Include minimal layout in frame responses by kevinmcconnell · Pull Request #428 · hotwired/turbo-rails · GitHub

Hi,

Interesting. I replied in the PR you linked a few weeks ago but the author of the PR didn’t mention what they’ve added would allow for this use case.

In a pre-frames world I would have added this into let’s say a new.html.erb page:

<%
  content_for(:title) { t('.title') }
  content_for(:meta_description) { t('.meta_description') }
%>

Adding these inside of a frame doesn’t cause them to be updated when navigating frames tho. But I didn’t look into what a “minimal layout” is yet.

Edit:

I defined my own layouts/turbo_rails/frame.html.erb based on that PR and modified it to be this for a quick test:

<html>
  <head>
    <meta name="alternative" content="present" />
    <%= yield :head %>
    <title><%= yield :title %></title>
    <meta name="description" content="<%= yield :meta_description %>">
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Then I added this inside of one of the frames:

  <%
    content_for(:title) { 'index' }
    content_for(:meta_description) { 'cool' }
  %>

Unfortunately it didn’t update upon page transition.

Although I am using a custom layout, if I explicitly reference this new frame layout then it updates.

My custom layout has:

<% content_for :content do %>
  <main>
    <p>A whole bunch of things are here, which I omit for the sake of this post.</p>
  </main>
<% end %>

<%= render template: 'layouts/application' %>

If I replace application with turbo_rails/frame then it updates but then the pages are missing a bunch of content since it’s not using my custom layout.

On second reading through this PR Include minimal layout in frame responses by kevinmcconnell · Pull Request #428 · hotwired/turbo-rails · GitHub it does appear that while the response from the server will contain the layout - the frontend JavaScript won’t do anything with it.

Turbo still excludes everything outside of the frame itself, except for specific items like Turbo-specific meta tags that it checks for. So although this change sounds a bit like what you’re describing, it’s not really the same thing, unfortunately.

I’m stuck with this too :cry:

Okay, so, this is not a good practice, and is not approved by HTML standard, and might break in future browsers, but putting the new title tag inside the frame tag works:

# layouts/_tab.html.erb
<%= turbo_frame_tag :tab_content do %>
  <head><title> <%= content_for :head_title %> </title></head>
  <%= yield %>
<% end %>

Edit:
https://html.spec.whatwg.org/multipage/semantics.html#the-head-element
This sorta use seems supported for iframes specifically and what we’re doing with turbo is pretty similar so, morally, I think, this is an acceptable hack.

Thanks. There’s also the meta description, rel canonical tag, opengraph tags and more that could change between transitioning pages with Turbo Frames.

I’m kind of surprised there’s no clean way to handle this yet at the library level since Turbo Frame page transitions with URL changes is a built-in feature of the library.

I found a solution from @tleish:

<head>
  <title id="page-title">My Title</title>
</head>
<body>
  <turbo-frame data-turbo-action="advance" ...>
  
  <turbo-frame>
</body>
<turbo-frame ...>
  <turbo-stream action="update" target="page-title">
    <template>New Title</template>
  </turbo-stream>

  Content...
<turbo-frame

He suggests you just put a turbo stream inside of the turbo frame. I’ve tried this and it works perfectly.

While this works for the title, it doesn’t seem to be working since the meta description tag has no “content” but a content attribute. I’m still looking for a solution here if anyone has one…