Skip to content

Commit 987ad6f

Browse files
timdorrhedgerh
authored andcommitted
Update docs to match the example code.
1 parent 499ccdc commit 987ad6f

File tree

3 files changed

+113
-59
lines changed

3 files changed

+113
-59
lines changed

docs/recipes/ServerRendering.md

Lines changed: 100 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,44 @@
33
The most common use case for server-side rendering is to handle the _initial render_ when a user (or search engine crawler) first requests our app. When the server receives the request, it renders the required component(s) into an HTML string, and then sends it as a response to the client. From that point on, the client takes over rendering duties.
44

55
### Redux on the server
6+
67
When using a store like Redux, we must also send the initial state of our app along in our response. To do this, we need to: create a fresh, new Redux store instance on every request, optionally dispatch some actions, pull the state out of store, and then pass the state along to the client. On the client side, a new Redux store will be created and initialized with the state provided from the server.
78

89
Redux's **_only_** job on the server side is to provide the **initial state** of our app.
910

10-
-----
11-
12-
In the following recipe, we are going to look at how to set up server-side rendering, using the [Todo List app](../basics/ExampleTodoList.html) that we built in [Basics](../basics/) as a guide.
13-
1411
## Setting Up
1512

16-
### File Structure
17-
We are going to put the actions, reducers, and components from the 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.
18-
19-
server.jsx
20-
client/index.jsx
21-
shared/actions.js
22-
shared/reducers.js
23-
shared/containers/App.js
24-
shared/components/AddTodo.js
25-
shared/components/Footer.js
26-
shared/components/Todo.js
27-
shared/components/TodoList.js
28-
13+
In the following recipe, we are going to look at how to set up server-side rendering. We'll use the simplistic [Counter app](https://github.com/rackt/redux/tree/master/examples/counter) as a guide and show how the server can render state ahead of time based on the request.
2914

3015
### Install Packages
16+
3117
For this example, we'll be using [Express](http://expressjs.com/) as a simple web server.
3218

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

35-
36-
npm install --save express react-redux
21+
npm install --save react-redux express serve-static
3722

3823

3924
## The Server Side
4025

41-
**server.jsx**
26+
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. We do the same with the `serve-static` middleware to be able to serve up our client javascript bundle. If you're unfamiliar with Express or middleware, just know that our handleRender function will be called every time the server receives a request.
27+
28+
**server.js**
4229

43-
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.
4430
```js
45-
import Express from 'express';
46-
import React from 'react';
47-
import { createStore } from 'redux';
48-
import { Provider } from 'react-redux';
49-
import todoApp from './shared/reducers';
50-
import App from './shared/containers/App';
31+
import path from 'path';
32+
import Express from 'express';
33+
import React from 'react';
34+
import { createStore } from 'redux';
35+
import { Provider } from 'react-redux';
36+
import counterApp from './reducers';
37+
import App from './containers/App';
38+
39+
const app = Express();
40+
const port = 8080;
5141

52-
var app = Express();
42+
// Use this middleware to server up static files built into the dist directory
43+
app.use(require('serve-static')(path.join(__dirname, 'dist')));
5344

5445
// This is fired every time the server side receives a request
5546
app.use(handleRender);
@@ -58,55 +49,61 @@ app.use(handleRender);
5849
function handleRender(req, res) { // ... }
5950
function renderFullPage(html, initialState) { //... }
6051

61-
export default app;
52+
app.listen(port);
6253
```
6354
64-
**Handling The Request**
55+
### Handling The Request
6556
6657
The first thing that we need to do on every request is create a new Redux store instance. The only purpose of this store instance is to provide the initial state of our application.
6758
68-
When rendering, we will wrap `<App/>`, our root component, inside a `<Provider>` to make the store available to all components in the component tree.
59+
When rendering, we will wrap `<App/>`, our root component, inside a `<Provider>` to make the store available to all components in the component tree, as we saw in [Usage with React](/docs/basics/UsageWithReact.html).
6960
70-
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).
61+
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).
7162
7263
We then get the initial state from our Redux store using **store.getState()**. We will see how this is passed along in our `renderFullPage` function.
7364
7465
```js
7566
function handleRender(req, res) {
76-
// Create a new Redux store instance
77-
var store = createStore(todoApp);
7867

79-
// Render the component to a string
80-
var html = React.renderToString(
81-
<Provider store={store}>
82-
{ () => <App/> }
83-
</Provider>);
68+
// Create a new Redux store instance
69+
const store = createStore(counterApp);
70+
71+
// Render the component to a string
72+
const html = React.renderToString(
73+
<Provider store={store}>
74+
{ () => <App/> }
75+
</Provider>);
8476

85-
// Grab the initial state from our Redux store
86-
var initialState = store.getState();
77+
// Grab the initial state from our Redux store
78+
const initialState = store.getState();
8779

88-
res.send(renderFullPage(html, initialState));
89-
}
80+
// Send the rendered page back to the client
81+
res.send(renderFullPage(html, initialState));
82+
}
9083
```
9184
92-
**Inject our initial component HTML and state**
85+
### Inject our initial component HTML and state
9386
9487
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__`.
9588
9689
The initialState will then be available on the client side by accessing `window.__INITIAL_STATE__`.
9790
91+
We also include our bundle file for the client-side application via a script tag. The `serve-static` middleware included above will serve up this file. We'll see what that file contains in just a bit.
92+
9893
```js
9994
function renderFullPage(html, initialState) {
10095
return `
10196
<!doctype html>
10297
<html>
98+
<head>
99+
<title>Redux Universal Example</title>
100+
</head>
103101
<body>
104102
<div id="app">${html}</div>
105103
<script>
106-
${/* put this here for the client to pick up, you'll need your
107-
components to pick up this stuff on the first render */}
108104
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
109105
</script>
106+
<script src="/bundle.js"></script>
110107
</body>
111108
</html>
112109
`;
@@ -118,36 +115,83 @@ function renderFullPage(html, initialState) {
118115
119116
The client side is very straightforward. All we need to do is grab the initial state from `window.__INITIAL_STATE__`, and pass it to our createStore function as the initial state.
120117
121-
Let's take a look at our new `client/index.jsx`:
118+
Let's take a look at our new client file:
119+
120+
**client.js**
122121
123-
**client/index.jsx**
124122
```js
125123
import React from 'react';
126124
import { createStore } from 'redux';
127125
import { Provider } from 'react-redux';
128-
import App from '../shared/containers/App';
129-
import todoApp from '../shared/reducers';
126+
import App from './containers/App';
127+
import counterApp from './reducers';
130128

131129
const initialState = window.__INITIAL_STATE__;
132130

133-
let store = createStore(todoApp, initialState);
131+
let store = createStore(counterApp, initialState);
134132

135133
let rootElement = document.getElementById('app');
136134
React.render(
137-
// The child must be wrapped in a function
138-
// to work around an issue in React 0.13.
139135
<Provider store={store}>
140136
{() => <App/>}
141137
</Provider>,
142138
rootElement
143139
);
144140

145141
```
146-
And that's it! That is all we need to do to implement server side rendering.
147142
148-
From here, the only other step is fetching any data that we need to generate our initial state.
143+
You can set up your build tool of choice (webpack, browserify, etc.) to compile a bundle file into `dist/bundle.js`.
144+
145+
When the page loads, the bundle file will be started up and [React.render](https://facebook.github.io/react/docs/top-level-api.html#react.render) will hook into the `data-react-id` attributes from the server-rendered HTML. This will connect our newly-started React instance to the virtual DOM used on the server. Since we have the same initial state for our Redux store and used the same code for all our view components, the result will be the same real DOM.
146+
147+
And that's it! That is all we need to do to implement server side rendering.
148+
149+
But the result is pretty vanilla. It essentially renders a static view from dynamic code. What we need to do next is build an initial state dynamically to allow that rendered view to be dynamic.
150+
151+
## Preparing the Initial State
152+
153+
Because the client side executes ongoing code, it can start with an empty initial state and obtain any necessary state on demand and over time. On the server side, execution is synchronous and we only get one shot to render our view. We need to be able to compile our initial state during the request, which will have to react to input and obtain external state (such as that from an API or database).
154+
155+
### Processing Request Parameters
156+
157+
The only input for server side code is the request made when loading up a page in your app in your browser. You may choose to configure the server during it's boot (such as when you are running in a development vs. production environment), but that configuration is static.
158+
159+
The request contains information about the URL requested, including any query parameters, which will be useful when using something like [react-router](https://github.com/rackt/react-router). It can also contain headers with inputs like cookies or authorization, or POST body data. Let's see how we can set the initial counter state based on a query parameter.
160+
161+
**server.js**
162+
163+
```js
164+
import qs from 'qs'; // Add this at the top of the file
165+
166+
function handleRender(req, res) {
167+
168+
// Read the counter from the request, if provided
169+
const params = qs.parse(req.query);
170+
const counter = parseInt(params.counter) || 0;
171+
172+
// Compile an initial state
173+
let initialState = { counter };
174+
175+
// Create a new Redux store instance
176+
const store = createStore(counterApp, initialState);
177+
178+
// Render the component to a string
179+
const html = React.renderToString(
180+
<Provider store={store}>
181+
{ () => <App/> }
182+
</Provider>);
183+
184+
// Grab the initial state from our Redux store
185+
const finalState = store.getState();
186+
187+
// Send the rendered page back to the client
188+
res.send(renderFullPage(html, finalState));
189+
}
190+
```
191+
The code reads from the Express `Request` object passed into our server middleware. The parameter is parsed into a number and then set in the initial state. If you visit [http://localhost:8080/?counter=100](http://localhost:8080/?counter=100) in your browser, you'll see the counter starts at 100. In the rendered HTML, you'll see the counter output as 100 and the `__INITIAL_STATE__` variable has the counter set in it.
192+
193+
### Async Data Fetching
149194
150-
## Async Data Fetching
151195
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**.
152196
153197
**examples**

examples/universal/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
"dependencies": {
1919
"babel": "^5.8.21",
2020
"express": "^4.13.3",
21+
"qs": "^4.0.0",
2122
"react": "^0.13.3",
2223
"react-redux": "^0.9.0",
2324
"redux": "^1.0.1",
25+
"redux-thunk": "^0.1.0",
2426
"serve-static": "^1.10.0"
2527
},
2628
"devDependencies": {

examples/universal/server.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from 'path';
22
import Express from 'express';
3+
import qs from 'qs';
34
import React from 'react';
45
import { createStore } from 'redux';
56
import { Provider } from 'react-redux';
@@ -17,8 +18,15 @@ app.use(handleRender);
1718

1819
function handleRender(req, res) {
1920

21+
// Read the counter from the request, if provided
22+
const params = qs.parse(req.query);
23+
const counter = parseInt(params.counter) || 0;
24+
25+
// Compile an initial state
26+
let initialState = { counter };
27+
2028
// Create a new Redux store instance
21-
const store = createStore(counterApp);
29+
const store = createStore(counterApp, initialState);
2230

2331
// Render the component to a string
2432
const html = React.renderToString(
@@ -27,10 +35,10 @@ function handleRender(req, res) {
2735
</Provider>);
2836

2937
// Grab the initial state from our Redux store
30-
const initialState = store.getState();
38+
const finalState = store.getState();
3139

3240
// Send the rendered page back to the client
33-
res.send(renderFullPage(html, initialState));
41+
res.send(renderFullPage(html, finalState));
3442
}
3543

3644
function renderFullPage(html, initialState) {

0 commit comments

Comments
 (0)