Module: UniversalRenderer::Renderable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/universal_renderer/renderable.rb
Overview
Controller-side entry point for server-side rendering. Drive it either declaratively with ClassMethods#enable_ssr, or imperatively by calling #render_ssr from the action. A failed or unconfigured render is a no-op: #ssr? returns false and the layout falls back to client-side rendering.
Defined Under Namespace
Modules: ClassMethods, Streaming
Constant Summary collapse
- NON_PAGE_RENDER_OPTIONS =
Render options that mean "this is not a page". The request format is still HTML for a
render json:inside a form post, or for a Turbo Frame, so the format check alone does not catch them. %i[ body file inline js json nothing partial plain xml ].freeze
Instance Method Summary collapse
-
#add_prop(key_or_hash, data_value = nil) ⇒ void
Adds a prop or a hash of props to be sent to the SSR service.
-
#add_query_data(query_key, data) ⇒ void
Adds a React Query cache entry that can be hydrated on SSR/client boot.
-
#push_prop(key, value_to_add) ⇒ void
Allows a prop to be treated as an array, pushing new values to it.
- #render(**options) ⇒ Object
-
#render_ssr(props = nil) ⇒ UniversalRenderer::SSR::Response?
(also: #fetch_ssr)
Fetches the SSR payload for the current request and remembers it, so the view helpers and #ssr? can see it.
-
#ssr? ⇒ Boolean
Whether this request has server-rendered content to emit.
-
#ssr_props ⇒ Hash
The props accumulated for this request.
-
#ssr_response ⇒ UniversalRenderer::SSR::Response?
The payload from the most recent #render_ssr, or nil if none succeeded.
-
#ssr_streaming? ⇒ Boolean
Whether this request is being streamed.
Instance Method Details
#add_prop(key_or_hash, data_value = nil) ⇒ void
This method returns an undefined value.
Adds a prop or a hash of props to be sent to the SSR service. Props are deep-stringified if a hash is provided.
153 154 155 156 157 158 159 |
# File 'lib/universal_renderer/renderable.rb', line 153 def add_prop(key_or_hash, data_value = nil) if data_value.nil? && key_or_hash.is_a?(Hash) ssr_props.merge!(key_or_hash.deep_stringify_keys) else ssr_props[key_or_hash.to_s] = data_value end end |
#add_query_data(query_key, data) ⇒ void
This method returns an undefined value.
Adds a React Query cache entry that can be hydrated on SSR/client boot.
Entries accumulate under the react_query prop as
{ "query_key" => [...], "data" => ... }. The NPM package's
hydrateReactQuery(props, queryClient) consumes exactly this shape — use
it in setup rather than reimplementing the loop.
207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/universal_renderer/renderable.rb', line 207 def add_query_data(query_key, data) parts = query_key.is_a?(Array) ? query_key : [query_key] # React Query compares keys structurally, so numeric parts have to stay # numeric. `deep_stringify_keys` below only touches hash keys. normalized = parts.map { |part| part.is_a?(Symbol) ? part.to_s : part } push_prop( :react_query, { query_key: normalized, data: data }.deep_stringify_keys ) end |
#push_prop(key, value_to_add) ⇒ void
This method returns an undefined value.
Allows a prop to be treated as an array, pushing new values to it.
If the prop does not exist or is nil, it's initialized as an empty array.
If the prop exists but is not an array (e.g., set as a scalar by add_prop),
its current value will be converted into the first element of the new array.
If value_to_add is an array, its elements are concatenated to the existing array.
Otherwise, value_to_add is appended as a single element.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/universal_renderer/renderable.rb', line 178 def push_prop(key, value_to_add) props = ssr_props prop_key = key.to_s current_value = props[prop_key] if current_value.nil? props[prop_key] = [] elsif !current_value.is_a?(Array) props[prop_key] = [current_value] end # At this point, props[prop_key] is guaranteed to be an array. if value_to_add.is_a?(Array) props[prop_key].concat(value_to_add) else props[prop_key] << value_to_add end end |
#render(**options) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/universal_renderer/renderable.rb', line 127 def render(*, **) return super unless ssr_enabled_for_request? return super if .keys.intersect?(NON_PAGE_RENDER_OPTIONS) # No layout, so nothing calls the helpers that would emit the payload. return super if [:layout] == false if ssr_streaming? success = render_ssr_stream(*, **) super unless success else render_ssr super end end |
#render_ssr(props = nil) ⇒ UniversalRenderer::SSR::Response? Also known as: fetch_ssr
Fetches the SSR payload for the current request and remembers it, so the view helpers and #ssr? can see it. Idempotent.
73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/universal_renderer/renderable.rb', line 73 def render_ssr(props = nil) return @_ssr_response if defined?(@_ssr_response) add_prop(props) if props.present? @_ssr_response = UniversalRenderer::Client::Base.call(request.original_url, ssr_props) @ssr = @_ssr_response # pre-0.6 layouts read this ivar @_ssr_response end |
#ssr? ⇒ Boolean
Whether this request has server-rendered content to emit. Use it to pick between a hydration entry point and a client-render entry point.
True while streaming too, where the HTML arrives after the layout renders.
103 104 105 |
# File 'lib/universal_renderer/renderable.rb', line 103 def ssr? ssr_streaming? || ssr_response.present? end |
#ssr_props ⇒ Hash
The props accumulated for this request. Mutating the returned hash is supported, but prefer #add_prop / #push_prop / #add_query_data.
rubocop:disable Naming/MemoizedInstanceVariableName -- the ivar name is part of the pre-0.6 surface; layouts and specs in the wild read it.
60 61 62 |
# File 'lib/universal_renderer/renderable.rb', line 60 def ssr_props @universal_renderer_props ||= {} end |
#ssr_response ⇒ UniversalRenderer::SSR::Response?
Returns The payload from the most recent #render_ssr, or nil if none succeeded.
90 91 92 93 94 95 |
# File 'lib/universal_renderer/renderable.rb', line 90 def ssr_response return @_ssr_response if defined?(@_ssr_response) # Tolerate layouts and controllers that assigned @ssr by hand. @ssr end |
#ssr_streaming? ⇒ Boolean
Whether this request is being streamed.
False unless the request would also stream, so the layout never emits the
<!-- SSR_HEAD --> / <!-- SSR_BODY --> markers into a page no renderer
will see. A failed stream downgrades this for the same reason.
114 115 116 117 118 |
# File 'lib/universal_renderer/renderable.rb', line 114 def ssr_streaming? return @_ssr_streaming if defined?(@_ssr_streaming) self.class.ssr_streaming_preference.present? && ssr_enabled_for_request? end |