ruby-on-railsreactjsreact-routerreact-railsserver-rendering

Server rendering with rails + react-rails gem + react router


I have create this sample repo that use rails (v4.2.6) with react-rails (v1.6.2) and react-router (v2.0.0-rc5): https://github.com/pioz/rails_with_react_and_react_router_example

In the file app/views/application/react_entry_point.html.erb I render the component MountUp with

<%= react_component('MountUp', {}, {prerender: false}) %>

The component MountUp render my router:

class MountUp extends React.Component {
  render() {
    return(
      <Router history={History}>
        <Route path="/" component={App}>
          <IndexRoute component={Index} />
          <Route path="/contact" component={Contact}/>
          <Route path="/about" component={About}/>
        </Route>
      </Router>
    )
  }
}

All works fine, but if I change the option prerender: true I get a strange error React::ServerRendering::PrerenderError in Application#react_entry_point:

Encountered error "Error: Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings." when prerendering MountUp with {}
Object.invariant [as default] ((execjs):21983:16)
createHashHistory ((execjs):24108:130)
(execjs):22633:20
wrapDeprecatedHistory ((execjs):25285:61)
createRouterObjects ((execjs):25259:23)
componentWillMount ((execjs):25228:38)
ReactCompositeComponentMixin.mountComponent ((execjs):8138:13)
wrapper [as mountComponent] ((execjs):3131:22)
Object.ReactReconciler.mountComponent ((execjs):6583:36)
ReactCompositeComponentMixin.mountComponent ((execjs):8153:35)
/Users/pioz/.rvm/gems/ruby-2.3.0/gems/execjs-2.6.0/lib/execjs/external_runtime.rb:39:in `exec'
...

How can I render this app server side? Is this the right way to do this?


Solution

  • Found a solution: we need two version of the component MountUp: a client version that use browser history and a server version that use a fake memory history. Here the two version of the component:

    // client version
    class MountUp extends React.Component {
      render() {
        return(
          <Router history={History}>
            <Route path="/" component={App}>
              <IndexRoute component={Index} />
              <Route path="/contact" component={Contact}/>
              <Route path="/about" component={About}/>
            </Route>
          </Router>
        )
      }
    }
    
    
    // server version
    class MountUp extends React.Component {
      render() {
        return(
          <Router history={createMemoryHistory(this.props.path)}>
            <Route path="/" component={App}>
              <IndexRoute component={Index} />
              <Route path="/contact" component={Contact}/>
              <Route path="/about" component={About}/>
            </Route>
          </Router>
        )
      }
    }
    

    We need also to create the memory history with the url path equal to the request: to do this we can pass to the component a new prop path with the path of the request:

    <%= react_component('MountUp', {path: request.path}, {prerender: true}) %>