class AdminController < ApplicationController
before_action :authenticate_admin!
before_action :disable_turbo_cache, only: %i[dashboard moderation_queue]
helper_method :turbo_cache_control_tag
def dashboard
@pending_payouts = Payout.pending.sum(:amount_cents)
@open_reports = Report.open.count
end
def moderation_queue
@flags = Flag.unresolved.order(created_at: :asc).limit(50)
end
private
def disable_turbo_cache
@turbo_no_cache = true
end
def turbo_cache_control_tag
return unless @turbo_no_cache
tag.meta(name: "turbo-cache-control", content: "no-preview")
end
end
<!DOCTYPE html>
<html>
<head>
<title>Admin · <%= content_for(:title) || "Console" %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= turbo_cache_control_tag %>
<%= stylesheet_link_tag "admin", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body data-controller="turbo-cache"
data-turbo-cache-reset-value="<%= @turbo_no_cache ? true : false %>">
<%= render "admin/nav" %>
<main class="admin-shell">
<%= yield %>
</main>
</body>
</html>
import { Controller } from "@hotwired/stimulus"
import { Turbo } from "@hotwired/turbo-rails"
export default class extends Controller {
static values = { reset: Boolean }
connect() {
this.exempt = this.exempt.bind(this)
this.beforeCache = this.beforeCache.bind(this)
document.addEventListener("turbo:load", this.exempt)
document.addEventListener("turbo:before-cache", this.beforeCache)
}
disconnect() {
document.removeEventListener("turbo:load", this.exempt)
document.removeEventListener("turbo:before-cache", this.beforeCache)
}
exempt() {
if (!this.resetValue) return
Turbo.cache.exemptPageFromCache()
}
beforeCache() {
if (!this.resetValue) return
// Purge stale snapshots so a Back navigation refetches live data.
Turbo.cache.clear()
}
}
Turbo Drive keeps a snapshot cache of visited pages and, on navigation, briefly renders a stale preview of a page before swapping in the fresh network response. On mostly-static pages this makes navigation feel instant, but on volatile admin dashboards — live queues, balances, moderation counts — the preview shows numbers that are already wrong, which reads as a bug. The fix is to opt those specific pages out of caching rather than disabling Turbo globally.
The AdminController tab centers the mechanism on a per-request flag. disable_turbo_cache sets @turbo_no_cache = true and is invoked from a before_action on the volatile actions, while a helper method turbo_cache_control_tag (exposed with helper_method) emits the correct <meta name="turbo-cache-control"> tag. Turbo recognizes three values: no-cache lets the page be restored from cache but never serves it as a preview, and no-preview is the stronger form that prevents the stale flash entirely — the dashboard uses no-preview so admins never see outdated figures.
The admin layout tab renders that tag inside <head> via yielding the helper, so the directive lands in the exact place Turbo reads it during a snapshot. Because the meta tag is evaluated per response, the same layout serves both cached and uncached pages depending on the controller flag; there is no separate template.
The turbo_cache_controller tab handles the harder case: pages cached before the flag existed, or fragments that mutate after load. It calls Turbo.cache.exemptPageFromCache() on turbo:load so the current page is never stored, and clears the whole snapshot cache on turbo:before-cache when the element carries a reset value. This is a Stimulus controller wired through importmap, giving fine-grained, element-level control without a full page directive.
The trade-off is real: opting out of preview caching costs the instant-render feel and forces a visible network wait, so it should be scoped narrowly to genuinely volatile views. A common pitfall is disabling caching site-wide, which quietly removes one of Turbo's main performance wins for pages that never needed it.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
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
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
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
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
<form data-controller="query-sync" data-action="change->query-sync#apply">
<select name="status" class="rounded border p-2">
<option value="">Any</option>
<option value="open">Open</option>
<option value="closed">Closed</option>
</select>
Filter UI that syncs query params via Stimulus (no front-end router)
Share this code
Here's the card — post it anywhere.