Server-side rendering is great for SEO, as well as progressive enhancement. When rendering on the server, we have a special set of constraints that we need to follow when building components and libraries.
tl;dr: Don't depend on the DOM/BOM; make sure your initial render is synchronous; don't mutate class variables; add a test to renderToString your server-side rendered page.
React components used on the server will be rendered to HTML by being passed to a renderToString() call, which calls componentWillMount() and render() once. This means that any components used by a server-side rendered section need to satisfy the following constraints:
- Must not rely on event handling for the initial render.
- Must not hook up any change listeners, or do anything asynchronous, inside
componentWillMount(). - All data must be available before the initial render.
- Mutating class-level variables should be avoided, as they will be persisted until a server restart.
- Must not change component ID during
componentWillMount. - Must not assume that the DOM/BOM exists in
render()andcomponentWillMount().
- Libraries that are used on the server should be mindful of the DOM not being available on the server, and should either: be modified to work without the DOM; have non-DOM specific fallbacks; or fail in an obvious manner.
- Singletons should be avoided, as once instantiated they will persist for the entire duration of the
nodeprocess.
In order to ensure that no module down the dependency chain breaks server-side rendering of your Calypso section or page, you should add a test to renderToString it. This way, when another developer modifies a dependency of your section in a way that would break server-side rendering, they'll be notified by a failed test.
Occasionally, it may be necessary to conditionally do something client-specific inside an individual source file. This is quite useful for libraries that are heavily DOM dependent, and require a different implementation on the server. Don't do this for React components, as it will likely cause reconciliation errors — factor out your dependencies instead.
Here's how your module's package.json should look, if you really want to do this:
{
"main": "index.node.js",
"browser": "index.web.js"
}
If you know that your code will never be called on the server, you can stub-out the module using NormalModuleReplacementPlugin in the config file, and make the same change in the Desktop config.
Awesome! Have a look at the Isomorphic Routing docs to see how to achieve this. In addition, there are a couple of things you'll need to keep in mind: if your components need dynamic data, we'll need to cache; renderToString is synchronous, and will affect server response time; you should add a test to your section that ensures that it can really be rendered with renderToString; if you want to SSR something logged in, dependency nightmares will ensue.
Please ping @ehg, @mcsf, @ockham, or @seear if you're thinking of doing this, or if you have any questions. :)