React-response
React-response provides an easy-to-use server-side rendering server for React. The goal of this project is to reduce the boilerplate you need for a universal React project. Instead of copy-pasting a server from somewhere, React-response makes it possible to quickly write a production-ready server-side React renderer yourself. This will enable you to focus on your app without worrying about how to implement server-side rendering.
The configuration of your React-response is done with familiar React components, kind of like React Router's route configuration. Almost all of the props have sensible defaults, so for the simplest apps you really don't have to do a lot to get a server running.
Production-ready stability is one of my end goals but we're still in the early days. Use in production at your own risk! If you do, do not hesitate to contact me with your experience.
What's it look like?
Glad you asked. The simplest hello World with React-response looks like this:
import { ReactServer, Response, serve, createServer } from 'react-response'
const server = createServer(
<ReactServer>
<Response />
</ReactServer>
)
serve(server)
Running that will display a built-in Hello World page in your browser at localhost:3000
.
The ReactServer
component instantiates a new Express server. The Response
component is responsible for rendering a React application at the specified Express route. The simplest example above demonstrates the built-in defaults, but most of React-response's behaviour is customizable. The full example illustrates all the ways you can customize React-response.
The full example
import { RouterContext } from 'react-router' // RouterContext is your root component if you're using React-router.
import routes from './routes' // React-router routes
import Html from './helpers/Html' // Your template component
import ReactResponse from 'react-response'
// Install the React-router-response if you use React-router
import createReactRouterResponse from 'react-response-router'
// Import all the things
const {
ReactServer,
Route,
Response,
serve,
createServer,
Middleware,
Static,
Favicon,
} = ReactResponse
/* Note that you need to install 'serve-favicon' and other middleware if you want to use them. */
const server = createServer(
// Set basic server config on the ReactServer component.
<ReactServer host="localhost" port="3000">
// This is an Express route with the default props. Middlewares need to be
// mounted inside a Route component.
<Route path="/" method="get">
// React-response ships with wrappers for some commonly used middleware.
<Middleware use={ compression() }/>
<Favicon path={ path.join(__dirname, '..', 'static', 'favicon.ico') }/>
<Static path={ path.join(__dirname, '..', 'static') }/>
// Set your template and handler.
// React-response uses simple built-in templates and handlers by default.
<Response template={ Html } handler={ createReactRouterResponse(routes) }>
// Pass the React component you want to render OR
// a custom render function as a child to Response.
{(renderProps, req, res) => {
// Return a map of props for the template component. The Html component
// takes one prop: `component` which should be a rendered React component.
return { component: ReactDOM.renderToString(
<RouterContext { ...renderProps } />
) }
}}
</Response>
</Route>
<Route path="/api"> // Many routes
<Static path={ path.join(__dirname, '..', 'static') }/>
<Middleware use={(req, res) => { /* Implement your APi proxy */ }} />
</Route>
</ReactServer>
)
serve(server)
Alright, this is more like it! As you can see, with React-response we attach middleware and app renderers to Routes, denoted by the <Route />
component. This is, as we saw in the simple example, completely optional.
Express middleware is painless to use through the <Middleware />
component. The middleware will be mounted on the route which the middleware component is a child of. Simply pass in a middleware function as the use
prop. Favicon
and Static
middleware components ship with React-response. They are simple wrappers for the generic middleware component.
The <Response />
component is where all the action happens. It receives your template component as a prop and the thing you want to render as a child. If you simply pass your app root component as a child to Response, Response will automatically render it to a string with ReactDOM. If you pass a function instead, it will be called with some props from the handler, as well as the request and response data from Express. This is called a custom render function.
The return value from your custom render function should be a map of props that will be applied to your template component. This is important!
React-response ships one handler, simpleResponse
. SimpleResponse will be used by default. Both modules export a factory function which should be called to produce the handler itself. This is your chance to supply additional props to the component that will be rendered! The reactRouterResponse factory expects your router config as its argument which will be used to serve your app. The simpleResponse factory simply splats any object you supply onto the rendered component.
To illustrate this, an example of the simpleResponse:
<Response handler={ createSimpleResponse({ foo: "bar" }) }>
<AppRoot />
/* EQUALS */
{ renderProps => ({
component: ReactDOM.renderToString(<AppRoot { ...renderProps } />)
}) }
</Response>
The custom render function in the above example will receive { foo: "bar" }
as the renderProps
argument. If you simply pass your root component as Response's child, the renderProps will be applied to it.
This is not very useful in the case of the simpleResponse
. If you use reactRouterResponse
(from the react-response-router
package), you give your route config to the factory and the handler outputs renderProps
from React-router. An example:
<Response handler={ createReactRouterResponse(routes) }>
<RouterContext />
/* EQUALS */
{ renderProps => ({
component: ReactDOM.renderToString(<RouterContext { ...renderProps } />)
}) }
</Response>
Note that <RouterContext />
will initially complain about missing props as you start the server if you do not give it the renderProps right away. This is OK and won't hinder the functionality of your app.
Again, remember to return a map of props for the template component. The simple Html skeleton that ships with React-response expects your stringified app as the component
prop, as illustrated in the examples.
The whole React-response setup is fed into the createServer
function. It compiles a fully-featured Express server from the components which you can feed to the serve
function.
A note on JSX
I know that some developers are not fond of JSX and prefer vanilla Javascript. My decision to use JSX and even React components for server configuration is bound to raise eyebrows. For me it comes down to preference, usability and how it looks. React-response is all about eliminating React boilerplate and easing the cognitive load of writing a server-side rendering server, so the last thing I wanted was a declarative and messy configuration.
The very first thing I did with React-response was to design the user interface of the configuration. While the data is not naturally nested like React Router's routes, I feel that using JSX and React components to build the server configuration gives React-response a distinct "React identity". Rendering React components on the server should just be a matter of composing them into the server configuration. It is also very easy to see what is going on from a quick glance at the configuration tree, and in my opinion it is much better than plain Javascript objects.
However, if you do not wish to use JSX, inspect the output of createServer
. It should be rather simple to re-create that object structure without JSX. Note that the components, like Middleware
and Response
, directly apply middleware and handlers to the Express instance.
Rest assured that I plan to fully support usage of React-response without React components, à la React Router. It just isn't a priority for the first few releases.
Getting started
First, install React-response:
npm install --save react-response
Make sure you have React-response's peerDependencies (react react-dom express) installed. Also install any middleware you want to use through the components.
If you are using React-router, install the React-router response handler for React-response. This can be achieved with:
npm i react-response-router --save
The full example above uses it.
Then, follow the examples above to set up your server config. When done, feed the config to createServer
and the output of createServer
into serve
.
Before unleashing node
on your server file, keep in mind that you need Babel to transpile the JSX. I suggest using babel-core/register
to accomplish this if you do not have transpiling enabled for your server-side code. Like this:
// server.babel.js
require('babel-core/register')
require('server.js') // Your server file
Then run that file with Node.
When run, React-response will output the URL where you can see your app. By default that is http://localhost:3000
.
How do I...
React-response was never meant to cover 100% of all use cases. I made it according to how I write universal React apps, but I have seen some projects that are simply out of React-response's scope. I am aiming for 80-90% of use cases. That said, React-response is quite accommodating as you can make your own response handlers and rendering functions. Also, many things seen in the server.js
file of various projects can be moved elsewhere, for example into the root component. The server should only render your app into a template and send the response on its way. The rest can be accomplished elsewhere.
... use Redux:
Easily! You need a custom render function where you configure your store, set up the <Provider />
and do data fetching. An example:
// server.js
<Response template={ YourTemplate } handler={ createReactRouterResponse(routes) }>
{ (renderProps, req, res) => {
// If you use react-router-redux, create history for its router state tracking.
const history = createMemoryHistory(req.url)
// The function that returns your store
const store = configureStore({ initialState: {}, history })
// You can also use a Root component that composes the Provider and includes your DevTools.
const component = (
<Provider store={store}>
<RouterContext { ...renderProps } />
</Provider>
)
// Return props for the template
return {
component,
store
}
}}
</Response>
// YourTemplate.js
class Html extends Component {
render() {
const {store, component} = this.props
// First, render your app to a string. See, no need to do even this in server.js!
const content = component ? ReactDOM.renderToString(component) : ''
return (
<html lang="en-us">
<head>
<meta charSet="utf-8"/>
</head>
<body>
// Plop in your app
<div id="root" dangerouslySetInnerHTML={{__html: content}}></div>
// Get the state from your store and put it into the template serialized:
<script dangerouslySetInnerHTML={{ __html: `window.__data=${ JSON.stringify(store.getState())};` }} charSet="UTF-8" />
</body>
</html>
)
}
}
You obviously also need to include your assets in that there Html template, but hopefully this illustrates how we can use Redux and also move some functionality away from the server file itself.
... create my own response handler?
Skipping over the othermost function, the factory, The response handler (reactRouterResponse for example) is a function that takes two functions as its arguments. The first one receives the template props from your rendering function and renders the template. It also receives the response object as its second argument. The second one is your custom rendering function, or the component you gave as a child to <Response />
wrapped in the default render function.
This function should return the route handler that will be attached to the Express route to handle the rendering of your app. Here is the response handler that ships with React-response:
// 1: response handler factory creator, 2: response handler factory, 3: response handler. SEE?! EASY!
export const createSimpleResponse = (renderProps = {}) => (renderTemplate, renderApp) => (req, res) => {
renderTemplate(renderApp(renderProps, req, res), res)
}
Absent from the description above is the factory function (the one that takes renderProps
). It is not strictly necessary in all cases, but it is a convenient way to inject stuff like React-router routes into the response handler scope. The <Response />
component provides the renderTemplate
and renderApp
functions. The renderTemplate function does two things: set response status to 200, and sends the response with the stringified template containing your app. A better name for it might be something like renderResponse
, which it was actually called before I changed it while writing this very chapter. Using your own template rendering function is not currently supported.
Future plans
This is but the very first release of React-response! Plans for the future include:
- Template engines like Jade and EJS
- Option to use something else than Express
- Add more
Response
components for the following needs:- Redux
- Redux React Router
- Production stability and more tests
- Integrated reverse proxy for external APIs
- Maybe a Relay server?
- Your suggestions!
- Other cool features
Components
ReactServer
- Props:
- host
- Type: string
- Default: '0.0.0.0'
- port
- Type: integer
- Default: '3000'
- host
- The wrapper component for React-response. This component instantiates
http
andExpress
.
- Props:
Route
- Props:
- path
- Type: string,
- Default: '/'
- method
- Type: string,
- Default: 'get'
- path
- This component establishes a route context for the application handler and middleware. If
get
requests to the/
route is all you need to serve, you can skip theRoute
component. Everything mounted as children for this component will be served under the specified path.
- Props:
Response
- Props:
- template
- Type: React component
- Default: simple React HTML template
- handler
- type: function
- default:
simpleResponse
- template
- Children:
- custom render function
- Type: function
- Arguments:
renderProps
,req
,res
- Arguments:
- Type: function
- OR
- your app root element
- Type: React element
- custom render function
- The Response component creates a route handler for rendering your app. The handler will handle the route specified by the
Route
component, orget
on/
by default. Without specifying a custom render function or your app root element as a child toResponse
, React-response will simply render a Hello World page.
- Props:
Middleware
- Props:
- use
- Type function (Express middleware)
- Default: dummy middleware
- use
- Directly applies the specified middleware into Express. Using this generic Middleware component you can accomplish many features that React-response does not currently cater to.
- Props:
createSimpleResponse
- Arguments:
- Object of props to apply to the component you are rendering
- Simply gives your component, stringified, to the template and sends that as the response.
- Arguments:
By inspecting the source code you might find out that the components can take more props than documented. These exist mainly for testing and decoupling purposes. Usage of undocumented props is not supported, but I am not your mother.
Test and build
I have a very simple React project set up in /test/consumer
that demonstrates how to use React-response. cd
into there and run node ./consumerenv.js
to run the test app. Unit tests are located in /test
and can be run with gulp test
. This project uses Tape and Sinon for testing.
To build the library simply run gulp build
.
Collaboration
PR's welcome! Please add tests for all changes and follow the general coding style. Semicolons are banned ;)