Isomorphic apps isn’t just about writing the code once; its more about maintaining a consistent codebase without having to double-check every single modification in your client or server code to make sure its consistent. In 2015, server-side rendering is simply not an option: being able to deliver HTML that is consistent with you client-side-JS-powered app is mandatory for any public-facing moderately large product, for SEO and mobile performance if not for desktop time to first interaction and accessibility. Anybody who has tried to use PhantomJS or Selenium for server-side rendering at scale also knows that its simply not viable in practice.
When React was released, and included `React.renderComponentToString` (now `React.renderToString`), my inner isomorphic sense immediatly started tingling.
However, beyond very simple demo cases, `React.renderToString` by itself doesn’t solve all the problems.
The most important problem is data fetching. If all your components are purely local, ie. they don’t depend on remote data fetching, then `React.renderToString` works great. But in the real world, you most certainly need to wire your components with data from your backend: SQL server, HTTP API, or whatever central data repository you’re using. And data fetching is, by nature, asynchronous. It can rely on `sql.query` or `xhr` or whatever fetching mechanism you’re leveraging. But `React.renderToString` is synchronous. This means it doesn’t make sense to write something like this:
`xhr.fetch` being asynchronous, it will still be pending by the time `render()` is called by `React.renderToString`. On the client side, the usual solution is to initialize with a dummy value (such as `null`) and to perform subsequent update/rerender once the data is available. But you can’t do this on the server, `React.renderToString` is merely a convenience helper. The VDOM is not actually fully constructed on the server. Its just a single-pass recursive call from the root.
The solution used by many reminds of the old `Backbone/Ruby on Rails` models bootstrapping technique. It consists in performing all the data fetching beforehand at the router level. Depending on the top level route, you predict which data will be needed, prefetch it, and then only call `React.renderToString`. The code will look like:
But in addition to this, you still have to declare the data dependencies at the component level, so that when the component tree changes, the data can be updated appropriately. You end up writing the same code twice: one at the router level, and one at the component level. This quickly becomes maintenance hell: everytime you modify an inner component, you need to make sure its data dependencies are reflected at the router level. Not to mention that in a React app, you certainly want to avoid using a top level router altogether and use local, nested routers implemented by a React component, such as ryanflorence’s react-router.
React Nexus allows you to do exactly this.
From the users perspective, all you have to do is to declare data dependencies at the component level, using a new lifecycle hook: `getNexusBindings`.
Using this single, side-effect free, synchronous lifecycle hook, React Nexus can figure out what exactly are you dependencies and how to resolve them.
On the client, getNexusBindings() is called in componentDidMount(), so that you component can be initialized asynchronously. It is also called in componentWillReceiveProps() so that new dependencies can be dynamically fetched.
On the server, the trick is even more useful. Here’s a diagram of what happens:
When you call `ReactNexus.prefetchApp`, React Nexus instanciates your root component, calls `getNexusBindings`, asynchronously resolves the dependencies, and only then calls its `render` method. It then recursively performs the same operation on each descendant, so that each component is rendered only after its data dependencies have been resolved. When the data dependencies of the whole tree have been resolved, then and only then it calls `React.renderToString`, guaranteed that all the inner data dependencies have been resolved. `ReactNexus.prefetchApp` yield an HTML string and a JSON blob containing all the prefetched data, which you can then inject in your servers’ response so you client can bootstrap itself on the seminal call to `React.render`.
Internally, React Nexus replicates the rendering cycle of React for you: it instanciates the React components using the same primitives as React (namely `instantiateReactComponent`) and enforces all its invariants and lifecycle methods calls. Basically, it decouples the components’ tree construction from the HTML rendering.
In addition to allowing you to define `getNexusBindings` (which can depend on props, unlike the Relay demo showcased at ReactConf – or even on state, although thats probably a terrible pattern), React Nexus exposes two methods: `applyNexusBindings` and `prefetchNexusBindings`. `applyNexusBindings` calls `getNexusBindings` and diffs its result with the result of the previous call. Added bindings are subscribed. Removed bindings are unsubscribed. Other are left untouched, minimizing the unnecessary allocations/deallocations. `prefetchNexusBindings` asynchronously waits for all current bindings to have provided an initial value.
It also comes with a nice, free and entirely optional bonus. React Nexus is built on top of Nexus Flux, my implementation of Flux based on duplex streams and a fully symmetrical, location-agnostic interpretation of Flux. Nexus Flux completely abstracts away the underlying transport or code execution location from the client components. From a components’ perspective, a Flux is just a collection of observable stores and a collection of dispatchable actions. You can therefore use a per-request, ‘local’ Flux instance to store per-request or per-session data (eg. locale, cookies info, etc) and access it from your components seemlessly on the client or on the server. It can also come handy when you want to mock the browser environment, eg. fake window size or scroll position which will be set to their actual values once rendered on the client but still deliver consistent HTML from the server. And you can use the Nexus Flux socket.io aka Flux over the Wire adapter to get real-time for free in addition to server-side rendering that ‘just works’: in addition to performing data fetching, the bindings are also leveraged to perform subsequent update. Each time the underlying data is updated, the component is re-rendered with the new data.
Like it? Hate it? Please feel free to comment on twitter at #reactnexus @elierotenberg.
You might be interested by the following links: