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) ⇒ void
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 ⇒ void
Removes Cloudflare cache headers and marks the response as not cacheable.
-
#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) ⇒ void
Applies headers for caching specifically for cloudflare's edge.
-
#skip_edge_cache! ⇒ void
Render-time signal that this response is per-user and must not be edge cached.
-
#skip_session ⇒ void
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) ⇒ void
This method returns an undefined value.
Declare actions as edge-cacheable. This automatically skips init_current_user
to prevent ghost guest creation on pages served from Cloudflare's CDN.
46 47 48 49 50 51 52 53 54 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 46 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
60 61 62 63 64 65 66 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 60 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 ⇒ void
This method returns an undefined value.
Removes Cloudflare cache headers and marks the response as not cacheable.
Use to bust edge caching on error responses.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 127 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) ⇒ void
This method returns an undefined value.
Applies headers for caching specifically for cloudflare's edge
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 77 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! ⇒ void
This method returns an undefined value.
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.
119 120 121 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 119 def skip_edge_cache! @_skip_edge_cache = true end |
#skip_session ⇒ void
This method returns an undefined value.
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
148 149 150 |
# File 'app/concerns/controllers/cloudflare_caching.rb', line 148 def skip_session request.[:skip] = true end |