Rails Turbo Frames: Dynamische UI's Bouwen Zonder JavaScript
Turbo Frames splitsen een pagina op in onafhankelijke secties die laden en updaten zonder volledige pagina-herladingen. Je krijgt de responsiviteit van een single-page app met server-gerenderde HTML. Geen React. Geen webpack-gedoe. Gewoon Rails views en een paar HTML-attributen.
Zo ziet een Turbo Frame eruit in de praktijk:
<%%= turbo_frame_tag "user_profile" do %>
<h2><%%= @user.name %></h2>
<p><%%= @user.bio %></p>
<%%= link_to "Bewerken", edit_user_path(@user) %>
<%% end %>
Als een gebruiker op “Bewerken” klikt, vervangt Rails alleen de content binnen dat frame — de rest van de pagina blijft staan. De browser herlaadt niet. De server rendert een volledig HTML-antwoord, maar Turbo haalt er alleen het matchende frame uit.
Hoe Frame Matching Werkt
Turbo Frames gebruiken een id-attribuut om content te matchen tussen de huidige pagina en het serverantwoord. Wanneer je op een link klikt of een formulier verstuurt binnen een frame:
- Turbo stuurt een standaard HTTP-request naar de server
- Ontvangt een volledig HTML-antwoord
- Zoekt de
<turbo-frame>met het matchendeidin het antwoord - Vervangt de content van het huidige frame met het response-frame
Je controller hoeft niets te weten over Turbo. Die rendert gewoon een normale view. De magie gebeurt client-side via HTML-attribuut matching.
# app/controllers/users_controller.rb
def edit
@user = User.find(params[:id])
# Niets bijzonders — dezelfde controller action als altijd
end
<%# app/views/users/edit.html.erb %>
<%%= turbo_frame_tag "user_profile" do %>
<%%= form_with model: @user do |f| %>
<%%= f.text_field :name %>
<%%= f.text_area :bio %>
<%%= f.submit "Opslaan" %>
<%% end %>
<%% end %>
Na het versturen van het formulier verwerkt Rails de update en rendert opnieuw de show- of edit-view. Turbo vervangt de frame-content. De URL in de browser verandert standaard niet — dat is bewust zo voor inline edits.
Lazy Loading van Frames
Frames kunnen hun content laden na de initiële pagina-render. Handig voor zware queries, data van derden of secties onder de vouw.
<%%= turbo_frame_tag "recent_activity", src: user_activity_path(@user), loading: :lazy do %>
<p>Activiteit laden...</p>
<%% end %>
Het loading: :lazy attribuut vertelt Turbo om de frame-content pas op te halen wanneer het zichtbaar wordt in de viewport. Zonder dit attribuut haalt Turbo het direct op na page load. De placeholder-content (“Activiteit laden…”) wordt getoond tot het antwoord binnenkomt.
Ik heb lazy frames gebruikt op een dashboard dat analytics ophaalde van drie verschillende services. De initiële laadtijd ging van 2,8 seconden naar 600ms. Elk analytics-paneel laadde onafhankelijk terwijl de gebruiker naar beneden scrollde.
Uit het Frame Breken
Standaard navigeren links binnen een Turbo Frame binnen dat frame. Soms moet een link het volledige pagina navigeren. Gebruik data-turbo-frame="_top":
<%%= turbo_frame_tag "search_results" do %>
<%% @results.each do |result| %>
<%%= link_to result.title, result_path(result), data: { turbo_frame: "_top" } %>
<%% end %>
<%% end %>
Dit is het patroon dat ik gebruik bij zoekinterfaces. Het zoekformulier en de resultaten zitten in een frame voor instant filtering, maar een klik op een resultaat laadt de volledige detailpagina.
Een Ander Frame Targeten
Links en formulieren kunnen een ander frame targeten dan waar ze in zitten. Dit maakt patronen als tabbed interfaces en master-detail layouts mogelijk:
<nav>
<%%= link_to "Profiel", user_profile_path, data: { turbo_frame: "main_content" } %>
<%%= link_to "Instellingen", user_settings_path, data: { turbo_frame: "main_content" } %>
<%%= link_to "Facturatie", user_billing_path, data: { turbo_frame: "main_content" } %>
</nav>
<%%= turbo_frame_tag "main_content" do %>
<%%= render "profile" %>
<%% end %>
Inline Editing Patroon
Waarschijnlijk de meest voorkomende Turbo Frame use case. Toon een read-only view, klik op bewerken, wissel naar een formulier, verstuur, wissel terug naar read-only.
<%# app/views/comments/_comment.html.erb %>
<%%= turbo_frame_tag dom_id(comment) do %>
<div class="comment">
<p><%%= comment.body %></p>
<%%= link_to "Bewerken", edit_comment_path(comment) %>
</div>
<%% end %>
De status: :unprocessable_entity (422) bij validatiefouten is cruciaal in Rails 8. Turbo verwacht dat mislukte form-submissions 422 retourneren, niet 200. Als je 200 retourneert bij een mislukt formulier, behandelt Turbo het als succes en ziet de gebruiker geen validatiefouten. Dit kostte me uren de eerste keer.
Turbo Frames vs Turbo Streams
Frames vervangen één rechthoekige sectie van de pagina. Streams kunnen meerdere delen tegelijk updaten. Gebruik frames wanneer:
- Je één afgebakende sectie bijwerkt
- De interactie een request-response patroon volgt
- Je de eenvoud van standaard controller actions wilt
Gebruik Turbo Streams wanneer een actie meerdere ongerelateerde pagina-onderdelen moet bijwerken.
In productie begin ik altijd met frames en stap ik alleen over op streams wanneer de UI multi-region updates vereist. Frames dekken 80% van de dynamische UI-behoeften met half de complexiteit.
Performance Overwegingen
Turbo Frames maken extra HTTP-requests. Elk frame met een src-attribuut is een aparte round trip.
Beperk dit met:
Russian doll caching — Cache frame-responses agressief. Omdat frames partial views renderen, werkt Rails fragment caching hier perfect.
Conditioneel laden — Voeg alleen src toe aan frames die de gebruiker daadwerkelijk ziet.
Response-grootte — Frame-responses moeten lean zijn. Gebruik een layout zonder navigatie en footer.
Veelgemaakte Fouten
Vergeten van het matchende frame ID. Als je response geen turbo-frame bevat met hetzelfde ID, logt Turbo een foutmelding en wordt het frame leeg.
200 gebruiken voor validatiefouten. Retourneer status: :unprocessable_entity voor mislukte form-submissions.
Frames nesten zonder nadenken. Links in een inner frame targeten standaard dat inner frame. Gebruik expliciete data-turbo-frame attributen.
Veelgestelde Vragen
Wanneer moet je Turbo Frames gebruiken?
Turbo Frames werken het best voor interacties die één paginasectie bijwerken: inline editing, tabbed content, zoekfilters met directe resultaten en lazy-loaded panelen.
Werken Turbo Frames met Rails API-only mode?
Nee. Turbo Frames vereisen server-gerenderde HTML-responses. API-only Rails apps die JSON retourneren kunnen geen Turbo Frames gebruiken.
Kan je Turbo Frames gebruiken met Devise?
Ja, maar let op redirect-gedrag. Devise redirects voor niet-geauthenticeerde requests kunnen in een frame terechtkomen. Gebruik data-turbo-frame="_top" op Devise-beschermde links.
Hoe beïnvloeden Turbo Frames SEO?
Content in lazy-loaded frames staat niet in de initiële HTML-response. Voor SEO-kritische content, render het in de initiële paginalading in plaats van in een lazy frame.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation