create-render-4r
v3.1.2
Published
Express.js middleware to render a React+Router+Redux+Radium app
Downloads
4
Readme
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.renderToString
for synchronous rendering - Instant re-hydration of app on browser using Redux initial state
- Set HTML
<head>
content:<title>
&meta
elements 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 [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] --save
Upgrading
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:3000
example.com
api.example.com:8443
velvet-glacier-1234.herokuapp.com
Example state.sourceRequest.protocol
values:
https
http
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;