hotwired / turbo

The speed of a single-page web application without having to write any JavaScript
https://turbo.hotwired.dev
MIT License
6.66k stars 422 forks source link

turbo-frame with data-turbo-permanent for load once behaviour #145

Open stevehodgkiss opened 3 years ago

stevehodgkiss commented 3 years ago

Hi,

I'm trying to load a turbo-frame[src] once on first page load by wrapping it in a div[data-turbo-permanent].

<div id="user-nav-container" data-turbo-permanent>
  <turbo-frame id="user-nav" src="user_nav.html">
    <a>Sign In</a>
  </turbo-frame>
</div>

The turbo-frame is currently being requested on each Turbo-intercepted click. The desired behaviour is to load the frame once and then not load it on subsequent Turbo navigation (i.e. not see any user_nav.html fetch requests), in the same way that data-turbo-permanent works with regular elements.

Without the wrapping data-turbo-permanent div Turbo makes 2 requests to user_nav.html on each visit. This might be a separate issue because I would expect only one request to be made in this scenario.

With the wrapping element this increases to 3 user_nav.html requests on the second Turbo click (screenshot).

Brave Screenshot

Example repo: https://github.com/stevehodgkiss/turbo-frame-permanent

Thanks!

stgm commented 3 years ago

It seems you're both providing content in the frame (i.e. <a>Sign In</a>) and also providing a src url. Generally speaking, these are two ways to do the same thing: provide content. In most cases, you should not provide a src attribute, unless you want to do things like lazy-loading.

Could it be that you're looking for something like the following?

<turbo-frame id="user-nav">
    <a href="user_nav.html">Sign In</a>
</turbo-frame>

PS. I may have been distracted by your href-less link in the example, and it could be that you want to do lazy-loading after all. Shout if that's the case!

stgm commented 3 years ago

I tried your example using the previous beta of Turbo (https://cdn.skypack.dev/@hotwired/turbo@7.0.0-beta.3) and that does seem to eliminate the problem.

stevehodgkiss commented 3 years ago

I tried your example using the previous beta of Turbo (https://cdn.skypack.dev/@hotwired/turbo@7.0.0-beta.3) and that does seem to eliminate the problem.

True, 7.0.0-beta.3 does eliminate the multiple XHR request issue present in the latest version.

PS. I may have been distracted by your href-less link in the example, and it could be that you want to do lazy-loading after all.

Yeah the goal is to render fully cacheable html pages and lazily load personalised fragments of html dynamically. Initial content inside the turbo-frame would contain a signed-out version, with the src attribute linking to an endpoint returning personalised partials if the user is signed in. Then when navigating with Turbo, these elements would need to be marked as data-turbo-permanent to prevent each page visit from requesting the same content again. We have this functionality implemented with a Stimulus controller & fetch currently, but I think it would be a great fit for turbo frames.

onordgren commented 3 years ago

Hello! Not sure if this will help you but I had a similar problem that I managed to solve just using fragments. In my application I want to be able to cache the whole page in a CDN but still have parts of the navigation dependent on the user session. I initially solved this using a fragment like below that would insert the user navigation based on the session.

<turbo-frame id="user_nav" src="user_nav.html"></turbo-frame>

This worked fine but I noticed that since Turbo caches the pages between visits the dynamic part of the navigation would flicker since it was there in the cached page and then removed before fetched again and updated. To resolve this I first made the whole frame (or a wrapper like you) permanent but the problem with this approach was that when loggin in/out the content wouldn't change unless forcing a page refresh.

To get around this I added a "placeholder" inside of the frame with the permanent attribute and a matching placeholder in the user_nav.html, something like below.

<turbo-frame id="user_nav" src="user_nav.html">
  <div id="user_nav_inner" data-turbo-permanent></div>
</turbo-frame>

<turbo-stream action="replace" target="user_nav">
  <template>
    <nav id="user_nav_inner" data-turbo-permanent>...</nav>
  </template>
</turbo-stream>

This will make Turbo keep the user-nav permanent when navigating the page but after each navigation do a new request to user_nav.html and replace the fragment with the actual user state.