Skip to content

Commit

Permalink
Add Server Rendering Recipe
Browse files Browse the repository at this point in the history
  • Loading branch information
hedgerh committed Aug 26, 2015
1 parent 5c997b6 commit d79a044
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* [Recipes](/docs/recipes/README.md)
* [Migrating to Redux](/docs/recipes/MigratingToRedux.md)
* [Reducing Boilerplate](/docs/recipes/ReducingBoilerplate.md)
* Server Rendering
* [Server Rendering](/docs/recipes/ServerRendering.md)
* [Computing Derived Data](/docs/recipes/ComputingDerivedData.md)
* [Writing Tests](/docs/recipes/WritingTests.md)
* [Troubleshooting](/docs/Troubleshooting.md)
Expand Down
189 changes: 187 additions & 2 deletions docs/recipes/ServerRendering.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,189 @@
# Server Rendering

Sorry, but we’re still writing this doc.
Stay tuned, it will appear in a day or two.
## Redux and Server Side Rendering
Redux is well-suited for server side rendering. This is because Redux does not rely on _singletons_.

Redux's job on the server side is to provide the initial state for the initial render of our app. To get this, we need to create a new instance of a Redux store for every new request.

In the following recipe, we are going to look at how to set up server side rendering. We are going to use the code from our Todo List app that we built in Basics as a guide, but shouldn't need to touch any of the actions, reducers, or components from that tutorial.

## Setting Up

### File Structure
We are going to move our actions, reducers, routes, and components from our Todo List app into a `shared/` folder, since they will be used by both the client and server. Note that we have moved our client-side entrypoint into the `client/` folder.

server.jsx
client/index.jsx
shared/actions.js
shared/reducers.js
shared/routes.js
shared/containers/App.js
shared/components/AddTodo.js
shared/components/Footer.js
shared/components/Todo.js
shared/components/TodoList.js


### Install Packages
For this example, we will be using [Express](http://expressjs.com/) as a simple web server.

We are going to be using [React Router v. 1.0.0-beta3](https://rackt.github.io/react-router/) to handle both our back-end and front-end routes.

We also need to install the React bindings for Redux, since they are not included in Redux by default.


npm install --save react redux express [email protected] react-redux


## The Server Side

**shared/routes.js**

These routes are used for **both** the client and server side. These are used by React Router to fetch the proper component for a specific route.
```js
import React from 'react';
import { DefaultRoute, Route } from 'react-router';
import App from 'containers/App';
import Footer from 'components/Footer';

export default (
<Route path="/" component={App}/>
);

```

**server.jsx**

The following is the outline for what our server side is going to look like. We are going to set up an [Express middleware](http://expressjs.com/guide/using-middleware.html) using [app.use](http://expressjs.com/api.html#app.use) to handle all requests that come in to our server. If you're unfamiliar with Express or middleware, just know that our handleRender function will be called every time the server receives a request.
```js
import Express from 'express';
import React from 'react';
import { Router } from 'react-router';
import Location from 'react-router/lib/Location';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import routes from './shared/routes';
import todoApp from './shared/reducers';

var app = Express();

// This is fired every time the server side receives a request
app.use(handleRender);

// We are going to fill these out in the sections to follow
function handleRender(req, res) { // ... }
function renderRoute(err, routeState) { //... }
function renderFullPage(html, initialState) { //... }

export default app;
```
**Handling The Request**
Every time a request is received, the first thing we need to do is create a new Redux store instance. The only purpose of this store instance is to provide the initial state of our application. Next, we pass our routes and the requested location to React Router. React Router is going to look at our **routes.js** and fetch the component that should handle the requested route.
```js
function handleRender(req, res) {
// Create a new Redux store instance
var store = createStore(todoApp);

// Create a new Location based on the requested path
var location = new Location(req.path, req.query);

// Run React Router with the given location and our routes
// renderRoute is the callback that will be fired
Router.run(routes, location, renderRoute);
};
```
**Rendering the Component**
The key step in server side rendering is to render the initial HTML of our component _**before**_ we send it to the client side. To do this, we use [React.renderToString](https://facebook.github.io/react/docs/top-level-api.html#react.rendertostring).
We wrap our root component in Provider to make the store available to all components in the component tree.
We also need to get the initial state from our Redux store, so that we can pass it along to the client as well. We get the initial state using **store.getState()**. We will see how this is passed along in our _renderFullPage_ function.
```js
function renderRoute(err, routeState) {
if(err) return console.error(err);

// Render the component to a string
var html = React.renderToString(
<Provider store={store}>
{() =>
<Router {...routeState} />
}
</Provider>);

// Grab the initial state from our Redux store
var initialState = store.getState();

res.send(renderFullPage(html, initialState));
}
```
**Inject our initial component HTML and state**
The final step on the server side is to inject our initial component HTML and initial state into a template to be rendered on the client side. To pass along the state, we add a `<script>` tag that will attach `initialState` to `window.__INITIAL_STATE__`.
The initialState will then be available on the client side by accessing `window.__INITIAL_STATE__`.
```js
function renderFullPage(html, initialState) {
return `
<!doctype html>
<html>
<body>
<div id="app">${html}</div>
<script>
${/* put this here for the client to pick up, you'll need your
components to pick up this stuff on the first render */}
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
</script>
</body>
</html>
`;
}
```
## The Client Side
The client side is very straightforward. All we need to do is _hydrate_ our store by grabbing `window.__INITIAL_STATE__` and passing it to our createStore function as the initial state.
We also need to pass the Router in as our child to Provider, rather than our App component.
Let's take a look at our new `client/index.jsx`:
**client/index.jsx**
```js
import React from 'react';
import { Router } from 'react-router';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from '../shared/containers/App';
import todoApp from '../shared/reducers';
import routes from '../shared/routes';

const initialState = window.__INITIAL_STATE__;

let store = createStore(todoApp, initialState);

let rootElement = document.getElementById('app');
React.render(
// The child must be wrapped in a function
// to work around an issue in React 0.13.
<Provider store={store}>
{() => <Router children={routes} />}
</Provider>,
rootElement
);

```
And that's it! That is all we need to do to implement server side rendering.
From here, the only other step is fetching any data that we need to generate our initial state.
## Async Data Fetching
Fetching data asynchronously during server side rendering is a common point of confusion. The first thing to understand is that you can fetch your data however you want, **as long as it is available _before_ we send our response to the client**.
**examples**

0 comments on commit d79a044

Please sign in to comment.