UPDATE: This project has been upgraded to React v0.14 – read the blog post about the upgrade.
This post is about a three steps project I initiated a few weeks ago. I completed the previous step about two weeks ago, so feel free to read my blog post about the front-end part : “Playing with ES6 (and React)”.
Before going further, a quote from stackoverflow (this is the most concise I found) :
My challenge was to make an isomorphic app, using React and ES6 :
- ES6 : I’ve been hearing about it for a while on meetups, articles I read, videos I watched – just like I knew it already – had to throw some real lines of codes 😉
- React : Same as above (moreover, I’ve been doing a lot of Angular the past two years and hearing more and more about React lately, so I had to try it for real)
- Isomorphic app : If I were about to use React in a project, it seems to be the perfect opportunity to try to implement it
I split the project into three parts :
- topheman-apis-proxy : The backend part (and a project on its own as well, since it’s extensible and configurable)
- topheman/react-es6 : The frontend part where I developed the app in ES6, using React, with the last step in mind – I only used isomorphic libraries
- topheman/react-es6-isomorphic : The expressJS server in the middle, that handles server-side rendering. I retrieved the frontend part from the previous step. At this point, any missing feature in the client was first developed on the frontend project repo then merged back to this one, to make sure that each project stays focused on its scope (one on frontend, the other on server-side rendering).
Try the app (there is an about page that will help you understand the difference between the two projects). If you’re into React and server-side rendering, take a look at my feedbacks …
NodeJS / ES6
When you developed your React components in ES6, to run them server-side, you can’t just use
require('node-jsx').install() (to make node understand
You’ll need to use
require('babel/register'); then node will not only understand
.jsx files but also any ES6 files you wrote and required through node’s
require() (you won’t even need to run node with the
--harmony flag that activates ES6 features implemented in V8 or use iojs).
NodeJS / Webpack
Webpack is a module bundler that comes with many great features. In some cases, they can become overwhelming.
Example : the
resolve.alias option that allows you to replace any module by an other (a feature you’ll find on every module loaders).
There won’t be any problem with the client-side bundling of your source code. But when you’ll want to reuse the same code on the server, you’ll have to override node’s
require(), passing it Webpack’s options – something that seems to be a lot messy (at least at the time I’m writting those lines).
The first version I made of the network layer service in charge of the API calls was using Webpack’s
resolve.alias. I quickly refactored it to avoid the problem I describe above reversing the design, by using injection. You can see the refactor on this commit on topheman/react-es6#6f78bdc.
Note : Things may have evolved since I wrote the code. There might be simple solutions to override node’s
require() so that it could take in account Webpack’s options. If you have insights, feel free to leave a comment.
Passing state to the client – Data serialization
If you simply return the server-side rendered html, React will re-render client-side your components (you would be loosing the interest of server-side rendering).
This is because when you do server-side rendering, you not only have to pass the markup but also the raw data (the state) on which was based the rendering, so that when the components mount, they already have a state – which is in sync with the existing markup. That way, you can tell React to use the state you passed and prevent it to make any API calls (more likely in
If you take a look at the source code of a page of my app, you’ll see a var in a script tag, containing those informations. There are other ways to do that (twitter puts it inside a hidden input …).
Here is the commit where I implemented it : topheman/react-es6-isomorphic#7fa4e3d.
- Since I’m using the React router on server (behind express router) and client side, I benefit directly from this router handling me the state (at least the router part : route, query, params …) on both server and client side.
- I’m passing the full result of the API call in the raw data (a large amount isn’t used on the view), this could be optimized – I’ll take care of that when I’ll implement flux …
Warning: render(): Target node has markup rendered by React, but there are unrelated nodes as well. This is most commonly caused by white-space inserted around server-rendered markup.
When React gives you warnings, it’s not three words but a little story as you can see … For this one, I wanted to keep my
.ejs layout template readable, so I just wrapped up the generated markup inside div tags.
I deploy to heroku via git. On my previous websites, I had grunt tasks that made a full build version of the project into a
/dist folder and then git pushed to deploy.
This time, I have a
postinstall hook in my
package.json which triggers my Webpack build task. So the client side bundles (js, css, assets …) are built on the production server, when I deliver a new release. This is another way to do it … If you take a look at the README, you’ll see there are still automated tasks to run the project in production mode locally.
On this project, the next step would be to implement flux – not that such a small app would absolutely need flux in order to manage all the states, but at least, it would be an opportunity to apply those concepts I’ve been reading about for the last weeks …
Since I accomplished the goal I had set for myself on that project, I’ll be more likely to try something else before coming back on it. I’ve read lots of good things about aurelia, taken a look at the source code … I think I’ll try that next 🙂 .
PS : I want to thank Nicolas Cuillery for his talk at React.js Paris “Live-coding sur React” (you can see a video of the same talk he gave at LyonJS – in french). When I attended the meetup, I didn’t start the isomorphic part yet, but it was pretty clear in my head except one thing : passing the raw data from the server to the client. I didn’t think a second about it and I’m sure it saved me some time …