Module: Controllers::CloudflareCaching
- Extended by:
- ActiveSupport::Concern
- Included in:
- ApplicationController
- Defined in:
- app/concerns/controllers/cloudflare_caching.rb
Overview
Cloudflare CDN caching utilities for edge caching
Usage:
1. Declare which actions are edge-cached (prevents ghost guest creation):
edge_cached # all actions
edge_cached only: %i[index show] # specific actions
2. In the controller action, set cache params:
set_cloudflare_cache(time_in_secs: 1.week.to_i, tags: ['product-123'])
To bust cache on error:
reset_cloudflare_cache
Why edge_cached matters:
Edge-cached pages skip the session cookie (Set-Cookie header) so Cloudflare
can cache them. Without edge_cached, init_current_user creates a guest user
in the database, then skip_session discards the session -- orphaning the guest.
This causes "ghost guest" proliferation.
Class Method Summary collapse
-
.edge_cached(only: nil) ⇒ Object
Declare actions as edge-cacheable.
Instance Method Summary collapse
-
#edge_cached_action? ⇒ Boolean
Returns true if the current action was declared via edge_cached.
- #reset_cloudflare_cache ⇒ Object
-
#set_cloudflare_cache(time_in_secs: 3.days.to_i, stale_time_in_secs: 1.day.to_i, stale_while_revalidate_in_secs: 1.day.to_i, public_cache_expires_in: 3.days, tags: nil) ⇒ Object
Applies headers for caching specifically for cloudflare's edge.
-
#skip_edge_cache! ⇒ Object
Render-time signal that this response is per-user and must not be edge cached.
-
#skip_session ⇒ Object
For some static content, we don't need to track the session This helps with picky CDNs throwing off the cache if a cookie is present.
Class Method Details
.edge_cached(only: nil) ⇒ Object
Declare actions as edge-cacheable. This automatically skips init_current_user
to prevent ghost guest creation on pages served from Cloudflare's CDN.
44 45 46 47 48 49 50 51 52 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 44 def edge_cached(only: nil) if only skip_before_action :init_current_user, only: only self._edge_cached_actions = Array(only).to_set(&:to_s).freeze else skip_before_action :init_current_user self._edge_cached_actions = :all end end |
Instance Method Details
#edge_cached_action? ⇒ Boolean
Returns true if the current action was declared via edge_cached
56 57 58 59 60 61 62 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 56 def edge_cached_action? config = self.class._edge_cached_actions return false if config.nil? return true if config == :all config.include?(action_name) end |
#reset_cloudflare_cache ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 116 def reset_cloudflare_cache return unless response # Remove all Cloudflare-specific cache headers response.delete_header('Cloudflare-CDN-Cache-Control') response.delete_header('Cache-Tag') # Explicitly tell Cloudflare to NOT cache this response # This prevents edge caching of error pages even if other headers suggest caching response.set_header('Cloudflare-CDN-Cache-Control', 'no-store') # Also prevent browser caching expires_now @edge_cached = false end |
#set_cloudflare_cache(time_in_secs: 3.days.to_i, stale_time_in_secs: 1.day.to_i, stale_while_revalidate_in_secs: 1.day.to_i, public_cache_expires_in: 3.days, tags: nil) ⇒ Object
Applies headers for caching specifically for cloudflare's edge
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 72 def set_cloudflare_cache(time_in_secs: 3.days.to_i, stale_time_in_secs: 1.day.to_i, stale_while_revalidate_in_secs: 1.day.to_i, public_cache_expires_in: 3.days, tags: nil) return unless response&.ok? # Render-time opt-out: any helper that produced per-user content during # this request (e.g. an inline lead form prefilled with @context_user # data) sets @_skip_edge_cache so we don't cache the personalized HTML # for everyone. return if @_skip_edge_cache warn_if_not_edge_cached expires_in public_cache_expires_in, public: true skip_session # Necessary otherwise you will not get the benefit of this method cdn_cache_controls = [] cdn_cache_controls << "max-age=#{time_in_secs}" cdn_cache_controls << "stale-if-error=#{stale_time_in_secs}" cdn_cache_controls << "stale-while-revalidate=#{stale_while_revalidate_in_secs}" if stale_while_revalidate_in_secs&.positive? response.set_header('Cloudflare-CDN-Cache-Control', cdn_cache_controls.join(',')) response.set_header('Cache-Tag', .join(',')) if .present? # Stamp the asset-manifest version onto THIS cacheable body so the www-edge # worker's ETag/304 + X-Asset-Version reflect the bundles this HTML references, # not the live manifest. It rides with the cached body, so a 3-day-stale edge # page carries its own (older) version and the worker stops 304-ing browsers # onto HTML that points at newer bundles. See www-edge-worker.js # handleAssetVersioning. response.set_header('X-Asset-Version', WebpackManifestLoader.asset_version) @edge_cached = true end |
#skip_edge_cache! ⇒ Object
Render-time signal that this response is per-user and must not be edge
cached. Safe to call multiple times; takes precedence over any later
set_cloudflare_cache call (typically run from an after_action).
Used by inline lead-form helpers so any page that embeds a personalized
form is automatically excluded from Cloudflare caching, regardless of
which controller renders it. The "lazy modal" lead-form pattern
(lead_form_modal / lazy_lead_form_modal) is unaffected — its form
is fetched per-request via Turbo Frame from an uncached endpoint, so
the host page can stay cached.
112 113 114 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 112 def skip_edge_cache! @_skip_edge_cache = true end |
#skip_session ⇒ Object
For some static content, we don't need to track the session
This helps with picky CDNs throwing off the cache if a cookie is present
135 136 137 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 135 def skip_session request.[:skip] = true end |