React server-side rendering
As already explained in the introduction to React, React views can be rendered on the server as well as on the client. Due to the virtual DOM this isn’t even especially complex. However it becomes a bit more difficult once the state between the client and server needs to be synchronized.
Why server-side rendering?
Rendering of React views directly on the server has several advantages:
- If the HTML is pre-rendered the browser’s performance increases as the browser has to do less work putting everything back together. In addition the HTML page can be cached by a CDN (content delivery network) and doesn’t have to be recreated on every call.
- Search engines cannot (won’t) interpret JavaScript, but only HTML code. Currently the only correct way is to render all search-engine relevant information on the server.
Rendering static markup
The renderToStaticMarkup
method receives a react component and returns the HTML as a String. This can be rendered with a simple template engine like Handlebars:
<div>{{{ markup }}}</div>
Reactive components
It is nice that components can be rendered on the server, but in most cases this is not enough. The complete interaction like event-bindings or prop and state changes are missing.
These components can only be integrated after React is aware that there are also components on the client-side.
The renderToString
method also returns HTML as a string, but compared to the static variant this method also enables client-side interaction.
The classic example of a incremental counter:
var Counter = React.createClass({ getInitialState: function() { return { count: this.props.initialCount }; }, _increment: function() { this.setState({ count: this.state.count + 1 }); }, render: function() { return <span onClick={this._increment}> {this.state.count} </span>; } });
This component can be rendered on the server as follows:
var React = require('react'); var Counter = React.createFactory(require("./counter")); var http = require('http'); var counterHtml = React.renderToString( Counter({ initialCount: 3 }) ); http.createServer(function(req, res) { if (req.url == '/') { res.send('<div id="container">' + counterHtml + '</div>'); } }).listen(3000);
If the website is visited via http://localhost:3000/ the browser shows the initially set number 3.
After a click on the span-element, which is supposed to trigger the click event, nothing happens. And there is an easy explanation: React does not know this component on the client yet and therefore is unable to bind event-handlers or conduct re-renderings.
The component must also be created in the browser in order to react to click-events:
var Counter = React.createFactory(require("./counter")); React.render(Counter({ initialCount: 3 }), document.getElementById("container"))
For this example we assume that React.js as well as the component have already been loaded in the browser.
This example contains a bit of magic: If the component is loaded with the same props as on the server it does not re-render. React recognizes that the DOM has not changed, but could change in the future and performs all necessary steps for binding the events.
If components aren’t re-rendered this has a positive impact on performance.
Synchronizing props
The principle behind props is easy: The client needs to pass on the props to the server:
// server.js // ... var props = { initialCount: 3 }; var counterHtml = React.renderToString( Counter(props) ); // ... res.send( '<div id="container">' + counterHtml + '</div>' + '<script>' + 'var Counter = React.createFactory(require("./counter"));' + 'React.render(Counter(' + safeStringify(props) + '), document.getElementById("container"))' + '</script>' );
Note: The safeStringify function enables the safe embedding of JSON into a JavaScript tag.
In line 5 the props { initialCount: 3 }
are passed onto the server-component. In line 12 it is passed on to the client.
The propos can also be placed in a separate script tag:
<script id="props" type="application/json"> {{{ theProps }}} </script> <script> var props = JSON.parse(document.getElementById("props").innerHTML); // ... </script>
Since the second script tag is now completely independent it can be placed directly in counter.jsx:
if (typeof window !== 'undefined') { var props = JSON.parse(document.getElementById("props").innerHTML); React.render(Counter(props), document.getElementById("container")); }
A little step further and we can place props directly in the render-method of the component:
render: function() { var json = safeStringify(this.props); var propStore = <script type="application/json" id="someId" dangerouslySetInnerHTML={{__html: json}}> </script>; return <div onClick={this._increment}> {propStore} {this.state.count} </div>; }
Putting the props into the render-method is not particularly beautiful, but has the advantage that all code responsible for server-side rendering is located in the React component itself.
Components into the browser
Other than React, also the browser needs to know the React components. In order not to load every component discretely tools like Browserify create complete bundles. Let’s go back to the (very rudimentary) example:
http.createServer(function(req, res) { if (req.url == '/') { // ... } else if (req.url == '/bundle.js') { res.setHeader('Content-Type', 'text/javascript') browserify() .require('./counter.js', {expose: 'counter'}) .transform({global: true}, literalify.configure({react: 'window.React'})) .bundle() .pipe(res) }
How does React synchronize props internally?
A component that has been rendered on the server via renderToString
contains a data-react-checksum attribute.
<div data-reactid=".pxv0hfgr28" data-react-checksum="85249504"> 4 </div>
A short look into the React source code (ReactServerRendering.js) shows what happens in the background:
function renderToString(component) { ... return transaction.perform(function() { var componentInstance = instantiateReactComponent(element, null); var markup = componentInstance.mountComponent(id, transaction, emptyObject); return ReactMarkupChecksum.addChecksumToMarkup(markup); }, null); ... }
The addChecksumToMarkup
function creates a Adler-32 Checksum of the HTML markup of the component and attaches it to the component which has been rendered on the server.
If this component is subsequently rendered on the client, the canReuseMarkup
(ReactMarkupChecksum.js) function tests for a re-rendering:
canReuseMarkup: function(markup, element) { var existingChecksum = element.getAttribute( ReactMarkupChecksum.CHECKSUM_ATTR_NAME ); existingChecksum = existingChecksum && parseInt(existingChecksum, 10); var markupChecksum = adler32(markup); return markupChecksum === existingChecksum; }
Conclusion
The example only shows how it can work – not necessarily how it has to be done.
There are more elegant ways to synchronize the server with the client, like fluxible-app (dehydration/rehydration). This way the methods dehydrate and rehydrate generate snapshots from the server-side state and send it to the browser.
Shared code between client and server brings a lot of advantages and as long as search engines cannot index JavaScript rendered HTML, server-side rendering is the only way to go.
About the Author
Roberto Bez is a passionate Webdeveloper and TechLead at HolidayCheck. For Roberto development is not only work or a job, but a great motivation and a new challange every day. Everything new and geeky, from new web-technologies to all kind of databases – he tries to introduce it in the daily development.