« Back to home

ReactJS server-side rendering with browser dependencies

Server-side rendering is one of the most powerful features of React. With renderToString() and browserify, a React component can be pre-rendered on the server to save page rendering time on the browser. There's an excellent example by Pete Hunt to demonstrate this pattern here.

However if your React component depends on browser-only libraries like jQuery, Bootstrap, or ValidateJS, it becomes a bit more complicated. Those libraries will complain about missing window and document objects and hence requiring them with browserify will fail. So something like this doesn't work:

client.js

var React = require('react');
var MyComponent = require('./my_component');

React.renderComponent(MyComponent, document.getElementById('placeholder'));

server.js

var MyComponent = require('./my_component');
var React = require('react');
var markup = React.renderToString(MyComponent);
// render markup...

my_component.js

// this library needs a `window` object to initialize, hence requiring will fail.
var FormValidator = require('./vendor/validate.js'); 

module.exports = React.createClass({
  componentDidMount: function() {
    // Set up form validation...
  },

  render: function() {
    return <form>My Form</form>
  }
});

One solution to this issue is to inject the browser dependencies into the React component and mock them during server rendering:

my_component.js

var FormValidator;
module.exports = React.createClass({
  componentWillMount: function() {
    FormValidator = this.props.FormValidator;
  },

  componentDidMount: function() {
    // Set up form validation...
  },

  render: function() {
    return <form>My Form</form>
  }
});

clien.js

var MyComponent = require('./my_component');
var FormValidator = require('./vendor/validate.js');
var React = require('react');

React.renderComponent(MyComponent, {
  FormValidator: FormValidator
}, document.getElementById('placeholder'));

In the server-side script, we just need to pass in null when calling renderToString:

var MyComponent = require('./my_component');
var React = require('react');
var markup = React.renderToString(MyComponent, {
  FormValidator: null
});

It works but can quickly become tedious when the number of frontend libraries increase. So instead of declaring the each dependencies directly in every React component, we can use a common module to hold references to all of them:

browser_dependencies.js

var dependencies = {};
module.exports = {
  setDependency: function(name, module) {
    dependencies[name] = module;
  },

  getDependency: function(name) {
    return dependencies[name];
  }
};

my_component.js

var $ = require('./browser_dependencies').getDependency('validator');
module.exports = React.createClass({
  componentDidMount: function() {
    // Set up form validation...
  },

  render: function() {
    return <form>My Form</form>
  }
});

And the client script is responsible for setting the dependencies accordingly:

var MyComponent = require('./my_component');
var validator = require('./vendor/validate.js');
var React = require('react');
var browserDependencies = require('./browser_dependencies');

browserDependencies.setDependency('validator', validator);

React.renderComponent(MyComponent, document.getElementById('placeholder'));

There's no need to do anything on the server side. Just render your component to string as normal.

Note that any set up code that references the browser dependencies MUST be put in componentDidMount and not componentWillMount. componentWillMount is called both on the server and client which will obviously fail when using with renderToString.

You can find the example code for this pattern here.

Comments

comments powered by Disqus