An empty frame with a src fills itself.
<%= turbo_frame_tag "sidebar", src: related_path do %>
<div class="spinner">loading…</div>
<% end %>
Reload the page with the ⟳ button and watch the wire: the page arrives, then a second request goes out for /related, and its turbo-frame#sidebar lands in the box. Whatever you put inside the tag shows until then.
The messages page painted without waiting for the sidebar. The slow part arrives a moment later, and it can be cached on its own. Turbo sets aria-busy="true" on the frame while it fetches and complete once it has content, so a one-line CSS rule gives you a loading state.
Below the fold, don't even ask.
<%= turbo_frame_tag "archive", src: archive_path, loading: "lazy" do %>
older messages load when you scroll here…
<% end %>
There's now an archive frame far down the messages page. It works like a lazy image: no request until it scrolls into view. Scroll the page down and watch the wire wake up.
Same trick for anything hidden at first: a tab that isn't selected, a <details> that starts closed, a modal. Those frames cost nothing until someone looks.
Split a page by how often each part changes.
A page that mixes "same for everyone" with "this user only" is hard to cache: one key per combination. Put the per-user part in a frame and the rest of the page can be served from a single cached copy. Or the reverse: a heavily personal page with one shared widget, framed.
Each eager frame is its own request, and content popping in late looks jittery. Lazy frames are free; eager ones aren't quite.