create-render-4r v3.1.2
Universal render for React+ 
Express.js middleware to render a 4r app server-side:
- React UI
- React Router
- Redux state container
- Radium styles

Features
- Drop-in server-side rendering for React+Router+Redux+Radium apps
- Uses React's rock-solid
ReactDOMServer.renderToStringfor synchronous rendering - Instant re-hydration of app on browser using Redux initial state
- Set HTML
<head>content:<title>&metaelements with DocumentMeta - Per-component data loading for the current route:
- Not-ok HTTP responses
- 301 for React Router's
<Redirect/>component - 404 for unmatched URLs
decorateResponse()with custom status code based on Redux state
- 301 for React Router's
Example Universal Web App
Demonstrates using this renderer in a working universal app.
Install
Add the module to package.json:
npm install create-render-4r --save
# Save peer dependencies for production (otherwise they're only dev dependencies):
npm install radium@0.15.x react@0.14.x react-document-meta@2.x react-dom@0.14.x react-redux@4.x react-router@1.x redux@3.x --saveUpgrading
Breaking changes are indicated by major versions. See UPGRADING
Usage
Basic usage in an Express server.js:
var express = require('express');
var createRender4r = require('create-render-4r');
var app = express();
// These are unique to your own app.
var routes = require('./my-routes');
var loadStore = require('./my-load-store');
var layoutHtml = require('./my-layout-html');
// Create the render middleware.
var render4r = createRender4r({
routes: routes,
loadStore: loadStore,
layoutHtml: layoutHtml
});
// Add the render for all requests.
app.use(render4r);
var PORT = process.env.PORT || 3000;
app.listen(PORT, function () {
console.log('Server listening on', PORT);
});API
createRender4r()
This function is used to generate the Express.js middleware.
It accepts a single argument, an object:
createRender4r({ routes, loadStore, layoutHtml, decorateResponse })routes
(Required) The <Router/> component
loadStore
(Required) A function taking initial state, returning the Redux store; created with Redux createStore
var Redux = require('redux');
var createStore = Redux.createStore;
var combineReducers = Redux.combineReducers;
var reducers = './my-reducers';
function loadStore(initialState) {
return createStore(combineReducers(reducers), initialState);
}layoutHtml
(Required) An HTML template function; this sample uses ES2015 module & template string syntx:
function layoutHtml(componentHTML, cleanInitialState, documentMeta) {
return `
<!DOCTYPE html>
<html>
<head>
${documentMeta}
<script type="application/javascript">
window.__INITIAL_STATE__ = ${cleanInitialState};
</script>
</head>
<body>
<div id="react-view">${componentHTML}</div>
<script type="application/javascript" src="/bundle.js"></script>
</body>
</html>
`;
}decorateResponse
(Optional) A side-effect function to update the response based on state:
function decorateResponse(res, state) {
/*
Example: set 404 response status when the item couldn't be fetched,
while the app still renders a nice Not Found message in the UI.
*/
var errText = state.item.get('error');
if (errText && /^404/.exec(errText)) {
res.status(404)
}
}Server-side async data loading
Per-component data loading for the current route.
fetchData()
Define this static (class) method on React components to enable Promise-based server-side fetching. You'll need to use a universal library like isomorphic-fetch within redux-thunk async action creators so they will work equivalently on the server-side & in web browsers.
fetchData(dispatch, props)
dispatch(required) the Redux store's dispatcherprops(required) the component's props
Must return a Promise to await completetion of the fetch. (This is what Redux thunk does.)
static fetchData(dispatch, props) {
return dispatch(ItemActions.getItem(props.params.id));
}sagasToRun()
Define this static (class) method on React components to enable Generator-based server-side fetching via Redux sagas.
sagasToRun(dispatch, props)
dispatch(required) the Redux store's dispatcherprops(required) the component's props
Must return an array of arrays of arguments for middleware.run.
static sagasToRun(dispatch, props) {
return [
[fetchThingsSaga],
[verifyAuthSaga, { userId: props.params.authUserId }]
];
}Additionally, the Redux store returned by loadStore() must expose the Saga middleware as store.sagaMiddleware:
// …
import createSagaMiddleware from 'redux-saga';
// …
export default function loadStore(...createStoreRestParams) {
const sagaMiddleware = createSagaMiddleware(...sagas);
// …
// This accessor must be added for Saga middleware to be used server-side:
store.sagaMiddleware = sagaMiddleware;
return store;
}Absolute URLs
Frequently, app code will need its absolute URL, canonical hostname & protocol to render links or make API requests.
This module includes a sourceRequest reducer to handle this state. It is captured on the server for each request as the Redux store is initialized.
Example state.sourceRequest.host values:
localhost:3000example.comapi.example.com:8443velvet-glacier-1234.herokuapp.com
Example state.sourceRequest.protocol values:
httpshttp
To use these values in an app:
Add this module's reducers to your store:
import { combineReducers } from 'redux' import { reducers as cr4rReducers } from 'create-render-4r' const rootReducer = combineReducers( Object.assign( cr4rReducers, // …& the app's reducers ) )Then access it in the Redux state:
// `store` is the Redux store const state = store.getState(); const proto = state.sourceRequest.protocol; const host = state.sourceRequest.host;