React Redux Universal Hot Example
This is a starter boilerplate app I've put together using the following technologies:
- Both client and server make calls to load data from separate API server
- React Router
- Babel for ES6 and ES7 magic
- Webpack for bundling
- Webpack Dev Middleware
- Webpack Hot Middleware
- Redux's futuristic Flux implementation
- Redux Dev Tools for next generation DX (developer experience). Watch Dan Abramov's talk.
- Redux Router Keep your router state in your Redux store
- ESLint to maintain a consistent code style
- redux-form to manage form state in Redux
- lru-memoize to speed up form validation
- multireducer to combine single reducers into one key-based reducer
- style-loader, sass-loader and less-loader to allow import of stylesheets in plain css, sass and less,
- bootstrap-sass-loader and font-awesome-webpack to customize Bootstrap and FontAwesome
- react-document-meta to manage title and meta tag information on both server and client
- webpack-isomorphic-tools to allow require() work for statics both on client and server
- mocha to allow writing unit tests for the project.
I cobbled this together from a wide variety of similar "starter" repositories. As I post this in June 2015, all of these libraries are right at the bleeding edge of web development. They may fall out of fashion as quickly as they have come into it, but I personally believe that this stack is the future of web development and will survive for several years. I'm building my new projects like this, and I recommend that you do, too.
Running Dev Server
npm run dev
The first time it may take a little while to generate the first
webpack-assets.json and complain with a few dozen
[webpack-isomorphic-tools] (waiting for the first Webpack build to finish) printouts, but be patient. Give it 30 seconds.
Using Redux DevTools
In development, Redux Devtools are enabled by default. You can toggle visibility and move the dock around using the following keyboard shortcuts:
- Ctrl+H Toggle DevTools Dock
- Ctrl+Q Move Dock Position
- see redux-devtools-dock-monitor for more detail information.
Building and Running Production Server
npm run buildnpm run start
If you are the kind of person that learns best by following along a tutorial, I can recommend the following.
- React Tutorial - Converting Reflux to Redux, by Matt Star
What initially gets run is
bin/server.js, which does little more than enable ES6 and ES7 awesomeness in the
server-side node code. It then initiates
server.js we proxy any requests to
/api/* to the
API server, running at
localhost:3030. All the data fetching calls from the client go to
Aside from serving the favicon and static content from
/static, the only thing
server.js does is initiate delegate
react-router. At the bottom of
server.js, we listen to port
3000 and initiate the API server.
Routing and HTML return
The primary section of
server.js generates an HTML page with the contents returned by
react-router. First we instantiate an
ApiClient, a facade that both server and client code use to talk to the API server. On the server side,
ApiClient is given the request object so that it can pass along the session cookie to the API server to maintain session state. We pass this API client facade to the
redux middleware so that the action creators have access to it.
Then we perform server-side data fetching, wait for the data to be loaded, and render the page with the now-fully-loaded
The last interesting bit of the main routing section of
server.js is that we swap in the hashed script and css from the
webpack-assets.json that the Webpack Dev Server – or the Webpack build process on production – has spit out on its last run. You won't have to deal with
webpack-assets.json manually because webpack-isomorphic-tools take care of that.
We also spit out the
redux state into a global
window.__data variable in the webpage to be loaded by the client-side
Server-side Data Fetching
react-router for a list of all the routes that match the current request and we check to see if any of the matched routes has a static
fetchData() function. If it does, we pass the redux dispatcher to it and collect the promises returned. Those promises will be resolved when each matching route has loaded its necessary data from the API server.
The client side entry point is reasonably named
client.js. All it does is load the routes, initiate
react-router, rehydrate the redux state from the
window.__data passed in from the server, and render the page over top of the server-rendered DOM. This makes React enable all its event listeners without having to re-render the DOM.
clientMiddleware.js, serves two functions:
- To allow the action creators access to the client API facade. Remember this is the same on both the client and the server, and cannot simply be
imported because it holds the cookie needed to maintain session on server-to-server requests.
- To allow some actions to pass a "promise generator", a function that takes the API client and returns a promise. Such actions require three action types, the
REQUESTaction that initiates the data loading, and a
FAILUREaction that will be fired depending on the result of the promise. There are other ways to accomplish this, some discussed here, which you may prefer, but to the author of this example, the middleware way feels cleanest.
Redux Modules... What the Duck?
src/redux/modules folder contains "modules" to help
isolate concerns within a Redux application (aka Ducks, a Redux Style Proposal that I came up with). I encourage you to read the
Ducks Docs and provide feedback.
This is where the meat of your server-side application goes. It doesn't have to be implemented in Node or Express at all. This is where you connect to your database and provide authentication and session management. In this example, it's just spitting out some json with the current time stamp.
Getting data and actions into components
To understand how the data and action bindings get into the components – there's only one,
InfoBar, in this example – I'm going to refer to you to the Redux library. The only innovation I've made is to package the component and its wrapper in the same js file. This is to encapsulate the fact that the component is bound to the
redux actions and state. The component using
InfoBar needn't know or care if
InfoBar uses the
redux data or not.
Now it's possible to render the image both on client and server. Please refer to issue #39 for more detail discussion, the usage would be like below (super easy):
let logoImage = ;
This project uses local styles using css-loader. The way it works is that you import your stylesheet at the top of the class with your React Component, and then you use the classnames returned from that import. Like so:
const styles = ;
Then you set the
className of your element to match one of the CSS classes in your SCSS file, and you're good to go!
<div => ... </div>
The project uses Mocha to run your unit tests, it uses Karma as the test runner, it enables the feature that you are able to render your tests to the browser (e.g: Firefox, Chrome etc.), which means you are able to use the Test Utilities from Facebook api like
To run the tests in the project, just simply run
npm test if you have
Chrome installed, it will be automatically launched as a test service for you.
To keep watching your test suites that you are working on, just set
singleRun: false in the
karma.conf.js file. Please be sure set it to
true if you are running
npm test on a continuous integration server (travis-ci, etc).
Deployment on Heroku
To get this project to work on Heroku, you need to:
- Remove the
"PORT": 8080line from the
heroku config:set NODE_ENV=production
heroku config:set NODE_PATH=./src
heroku config:set NPM_CONFIG_PRODUCTION=false
- This is to enable webpack to run the build on deploy.
The first deploy might take a while, but after that your
node_modules dir should be cached.
This project moves fast and has an active community, so if you have a question that is not answered below please visit our Discord channel or file an issue.
How do I disable the dev tools?
They will only show in development, but if you want to disable them even there, set
Although this isn't a library, we recently started versioning to make it easier to track breaking changes and emerging best practices.
I am more than happy to accept external contributions to the project in the form of feedback, bug reports and even better - pull requests :)
If you would like to submit a pull request, please make an effort to follow the guide in CONTRIBUTING.md.
Thanks for checking this out.
– Erik Rasmussen, @erikras