You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
4
4
5
5
### Redux on the server
6
+
6
7
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.
7
8
8
9
Redux's **_only_** job on the server side is to provide the **initial state** of our app.
9
10
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
-
14
11
## Setting Up
15
12
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.
29
14
30
15
### Install Packages
16
+
31
17
For this example, we'll be using [Express](http://expressjs.com/) as a simple web server.
32
18
33
19
We also need to install the React bindings for Redux, since they are not included in Redux by default.
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**
42
29
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.
44
30
```js
45
-
importExpressfrom'express';
46
-
importReactfrom'react';
47
-
import { createStore } from'redux';
48
-
import { Provider } from'react-redux';
49
-
importtodoAppfrom'./shared/reducers';
50
-
importAppfrom'./shared/containers/App';
31
+
importpathfrom'path';
32
+
importExpressfrom'express';
33
+
importReactfrom'react';
34
+
import { createStore } from'redux';
35
+
import { Provider } from'react-redux';
36
+
importcounterAppfrom'./reducers';
37
+
importAppfrom'./containers/App';
38
+
39
+
constapp=Express();
40
+
constport=8080;
51
41
52
-
var app =Express();
42
+
// Use this middleware to server up static files built into the dist directory
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.
67
58
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).
69
60
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).
71
62
72
63
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.
73
64
74
65
```js
75
66
functionhandleRender(req, res) {
76
-
// Create a new Redux store instance
77
-
var store =createStore(todoApp);
78
67
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
+
conststore=createStore(counterApp);
70
+
71
+
// Render the component to a string
72
+
consthtml=React.renderToString(
73
+
<Provider store={store}>
74
+
{ () =><App/> }
75
+
</Provider>);
84
76
85
-
// Grab the initial state from our Redux store
86
-
var initialState =store.getState();
77
+
// Grab the initial state from our Redux store
78
+
constinitialState=store.getState();
87
79
88
-
res.send(renderFullPage(html, initialState));
89
-
}
80
+
// Send the rendered page back to the client
81
+
res.send(renderFullPage(html, initialState));
82
+
}
90
83
```
91
84
92
-
**Inject our initial component HTML and state**
85
+
### Inject our initial component HTML and state
93
86
94
87
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__`.
95
88
96
89
The initialState will then be available on the client side by accessing `window.__INITIAL_STATE__`.
97
90
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
+
98
93
```js
99
94
functionrenderFullPage(html, initialState) {
100
95
return`
101
96
<!doctype html>
102
97
<html>
98
+
<head>
99
+
<title>Redux Universal Example</title>
100
+
</head>
103
101
<body>
104
102
<div id="app">${html}</div>
105
103
<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 */}
@@ -118,36 +115,83 @@ function renderFullPage(html, initialState) {
118
115
119
116
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.
120
117
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**
122
121
123
-
**client/index.jsx**
124
122
```js
125
123
importReactfrom'react';
126
124
import { createStore } from'redux';
127
125
import { Provider } from'react-redux';
128
-
importAppfrom'../shared/containers/App';
129
-
importtodoAppfrom'../shared/reducers';
126
+
importAppfrom'./containers/App';
127
+
importcounterAppfrom'./reducers';
130
128
131
129
constinitialState=window.__INITIAL_STATE__;
132
130
133
-
let store =createStore(todoApp, initialState);
131
+
let store =createStore(counterApp, initialState);
134
132
135
133
let rootElement =document.getElementById('app');
136
134
React.render(
137
-
// The child must be wrapped in a function
138
-
// to work around an issue in React 0.13.
139
135
<Provider store={store}>
140
136
{() =><App/>}
141
137
</Provider>,
142
138
rootElement
143
139
);
144
140
145
141
```
146
-
And that's it! That is all we need to do to implement server side rendering.
147
142
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
+
importqsfrom'qs'; // Add this at the top of the file
165
+
166
+
functionhandleRender(req, res) {
167
+
168
+
// Read the counter from the request, if provided
169
+
constparams=qs.parse(req.query);
170
+
constcounter=parseInt(params.counter) ||0;
171
+
172
+
// Compile an initial state
173
+
let initialState = { counter };
174
+
175
+
// Create a new Redux store instance
176
+
conststore=createStore(counterApp, initialState);
177
+
178
+
// Render the component to a string
179
+
consthtml=React.renderToString(
180
+
<Provider store={store}>
181
+
{ () =><App/> }
182
+
</Provider>);
183
+
184
+
// Grab the initial state from our Redux store
185
+
constfinalState=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
149
194
150
-
## Async Data Fetching
151
195
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**.
0 commit comments