ruby erb 78 lines · 4 tabs

Render without layout for Turbo Frame requests

Shared by codesnips Jan 2026
4 tabs
module LayoutlessTurboFrame
  extend ActiveSupport::Concern

  included do
    layout :layout_for_turbo
  end

  private

  def layout_for_turbo
    # false disables the layout entirely; nil defers to normal resolution.
    turbo_frame_request? ? false : nil
  end

  def turbo_frame_request?
    request.headers["Turbo-Frame"].present?
  end
end
4 files · ruby, erb Explain with highlit

Turbo Frames replace a single fragment of the page, so responding to a frame request with the full application layout wastes bandwidth and can even break the DOM when the returned markup includes a second <body> or duplicate navigation. The Rails convention is to detect the frame request server-side and render the action's template without any layout, returning just the <turbo-frame> and its contents. These files show a small, reusable way to do that across every controller.

In LayoutlessTurboFrame concern, the logic is packaged as a controller concern so it can be included once in ApplicationController. Turbo sends the header Turbo-Frame on every frame-driven request, exposed by the turbo_frame_request? helper. The concern registers a layout block via layout :layout_for_turbo, which returns false (meaning "no layout") when the request came from a frame and nil otherwise, letting Rails fall back to its normal layout resolution. Using nil rather than "application" is deliberate: it preserves per-controller layout declarations instead of hard-coding one.

ApplicationController simply includes the concern, so the behavior is inherited everywhere without touching individual actions. This keeps the decision in one place and avoids scattering render layout: false calls through the codebase, which is easy to forget and hard to audit.

MessagesController demonstrates the payoff. The create action responds to a normal HTML form with a redirect, but when the same form lives inside a <turbo-frame id="new_message">, Turbo intercepts the submission and the controller renders create.turbo_stream or the frame template directly. Because the layout is suppressed automatically, the response body is only the frame markup, and edit likewise returns just the inline edit form.

messages/edit.html.erb shows the matching view: the entire template is wrapped in turbo_frame_tag, so a full-page visit still renders inside the layout while a frame request returns the same <turbo-frame> alone. The trade-off is subtle — the template must be self-contained within its frame, and any layout-provided assets (flash, nav) will not appear in frame responses, which is exactly what makes them cheap. This pattern is the idiomatic bridge between server-rendered Rails and Hotwire's partial-page updates.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Semantic HTML Example</title>

Semantic HTML5 elements and accessibility best practices

html html5 semantics
by Alex Chang 2 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Render without layout for Turbo Frame requests — share card
Link copied