Skip to content

Latest commit

 

History

History
110 lines (79 loc) · 4.85 KB

server-side-rendering.md

File metadata and controls

110 lines (79 loc) · 4.85 KB

Server-Side Rendering

You can render React components inside your Rails server with prerender: true:

<%= react_component('HelloMessage', {name: 'John'}, {prerender: true}) %>
<!-- becomes: -->
<div data-react-class="HelloMessage" data-react-props="{&quot;name&quot;:&quot;John&quot;}">
  <h1>Hello, John!</h1>
</div>

(It will also be mounted by the UJS on page load.)

Server rendering is powered by ExecJS and subject to some requirements:

  • react-rails must load your code. By convention, it uses server_rendering.js, which was created by the install task. This file must include your components and their dependencies (eg, Underscore.js).
  • Requires separate compilations for server & client bundles (see Webpack config)
  • Your code can't reference document or window. Prerender processes don't have access to document or window, so jQuery and some other libs won't work in this environment :(

ExecJS supports many backends. CRuby users will get the best performance from mini_racer.

Configuration

Server renderers are stored in a pool and reused between requests. Threaded Rubies (eg jRuby) may see a benefit to increasing the pool size beyond the default 0.

These are the default configurations:

# config/application.rb
# These are the defaults if you don't specify any yourself
module MyApp
  class Application < Rails::Application
    # Settings for the pool of renderers:
    config.react.server_renderer_pool_size  ||= 1  # ExecJS doesn't allow more than one on MRI
    config.react.server_renderer_timeout    ||= 20 # seconds
    config.react.server_renderer = React::ServerRendering::BundleRenderer
    config.react.server_renderer_options = {
      files: ["server_rendering.js"],       # files to load for prerendering
      replay_console: true,                 # if true, console.* will be replayed client-side
    }
    # Changing files matching these dirs/exts will cause the server renderer to reload:
    config.react.server_renderer_extensions = ["jsx", "js"]
    config.react.server_renderer_directories = ["/app/assets/javascripts", "/app/javascript/"]
  end
end

JavaScript State

Some of ExecJS's backends are stateful (eg, mini_racer, therubyracer). This means that any side-effects of a prerender will affect later renders with that renderer.

To manage state, you have a couple options:

  • Make a custom renderer with #before_render / #after_render hooks as described below
  • Use per_request_react_rails_prerenderer to manage state for a whole controller action.

To check out a renderer for the duration of a controller action, call the per_request_react_rails_prerenderer helper in the controller class:

class PagesController < ApplicationController
  # Use the same React server renderer for the entire request:
  per_request_react_rails_prerenderer
end

Then, you can access the ExecJS context directly with react_rails_prerenderer.context:

def show
  react_rails_prerenderer           # => #<React::ServerRendering::BundleRenderer>
  react_rails_prerenderer.context   # => #<ExecJS::Context>

  # Execute arbitrary JavaScript code
  # `self` is the global context
  react_rails_prerenderer.context.exec("self.Store.setup()")
  render :show
  react_rails_prerenderer.context.exec("self.Store.teardown()")
end

react_rails_prerenderer may also be accessed in before- or after-actions.

Custom Server Renderer

react-rails depends on a renderer class for rendering components on the server. You can provide a custom renderer class to config.react.server_renderer. The class must implement:

  • #initialize(options={}), which accepts the hash from config.react.server_renderer_options
  • #render(component_name, props, prerender_options) to return a string of HTML

react-rails provides two renderer classes: React::ServerRendering::ExecJSRenderer and React::ServerRendering::BundleRenderer.

ExecJSRenderer offers two other points for extension:

  • #before_render(component_name, props, prerender_options) to return a string of JavaScript to execute before calling React.render
  • #after_render(component_name, props, prerender_options) to return a string of JavaScript to execute after calling React.render

Any subclass of ExecJSRenderer may use those hooks (for example, BundleRenderer uses them to handle console.* on the server).