0
# Server-Side Rendering
1
2
Comprehensive server-side rendering (SSR) support for React Loadable, enabling seamless hydration between server-rendered content and client-side lazy loading. This includes module tracking, preloading utilities, and bundle mapping.
3
4
## Capabilities
5
6
### Module Capture
7
8
Track which loadable components are rendered during server-side rendering to ensure the correct bundles are sent to the client.
9
10
```javascript { .api }
11
/**
12
* Component for capturing rendered modules during SSR
13
*/
14
class Capture extends React.Component {
15
static propTypes: {
16
/** Function called for each rendered module */
17
report: (moduleName: string) => void;
18
/** Child components to render */
19
children: React.ReactNode;
20
};
21
22
static childContextTypes: {
23
loadable: {
24
report: (moduleName: string) => void;
25
};
26
};
27
}
28
```
29
30
**Usage Examples:**
31
32
```javascript
33
import React from 'react';
34
import ReactDOMServer from 'react-dom/server';
35
import Loadable from 'react-loadable';
36
import App from './App';
37
38
// Server-side rendering with module capture
39
app.get('/', (req, res) => {
40
const modules = [];
41
42
const html = ReactDOMServer.renderToString(
43
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
44
<App />
45
</Loadable.Capture>
46
);
47
48
console.log('Rendered modules:', modules);
49
50
// Use modules to determine which bundles to include
51
const bundles = getBundles(stats, modules);
52
53
res.send(createHTML(html, bundles));
54
});
55
56
function createHTML(content, bundles) {
57
return `
58
<!DOCTYPE html>
59
<html>
60
<head><title>My App</title></head>
61
<body>
62
<div id="app">${content}</div>
63
${bundles.map(bundle =>
64
`<script src="${bundle.publicPath}"></script>`
65
).join('\n')}
66
<script src="/main.js"></script>
67
</body>
68
</html>
69
`;
70
}
71
```
72
73
### Server Preloading
74
75
Preload all loadable components on the server before rendering to ensure all components are available during SSR.
76
77
```javascript { .api }
78
/**
79
* Preloads all registered loadable components
80
* @returns Promise that resolves when all components are loaded
81
*/
82
function preloadAll(): Promise<void>;
83
```
84
85
**Usage Examples:**
86
87
```javascript
88
import express from 'express';
89
import Loadable from 'react-loadable';
90
import { renderApp } from './render';
91
92
const app = express();
93
94
// Preload all components before starting the server
95
Loadable.preloadAll().then(() => {
96
app.get('/', renderApp);
97
98
app.listen(3000, () => {
99
console.log('Server started on http://localhost:3000');
100
});
101
}).catch(err => {
102
console.error('Failed to preload components:', err);
103
process.exit(1);
104
});
105
106
// Alternative: preload on each request (not recommended for production)
107
app.get('/slow', async (req, res) => {
108
try {
109
await Loadable.preloadAll();
110
const html = renderToString(<App />);
111
res.send(html);
112
} catch (error) {
113
res.status(500).send('Server error');
114
}
115
});
116
```
117
118
### Client Hydration
119
120
Preload components that were server-rendered before hydrating the client application.
121
122
```javascript { .api }
123
/**
124
* Preloads components that are ready (webpack modules already available)
125
* Typically used on the client before hydration
126
* @returns Promise that resolves when ready components are loaded
127
*/
128
function preloadReady(): Promise<void>;
129
```
130
131
**Usage Examples:**
132
133
```javascript
134
// Client entry point
135
import React from 'react';
136
import ReactDOM from 'react-dom';
137
import Loadable from 'react-loadable';
138
import App from './App';
139
140
// Wait for loadable components before hydrating
141
Loadable.preloadReady().then(() => {
142
ReactDOM.hydrate(<App />, document.getElementById('app'));
143
});
144
145
// Alternative with error handling
146
Loadable.preloadReady()
147
.then(() => {
148
ReactDOM.hydrate(<App />, document.getElementById('app'));
149
})
150
.catch(err => {
151
console.error('Failed to preload components:', err);
152
// Fallback to client-side rendering
153
ReactDOM.render(<App />, document.getElementById('app'));
154
});
155
```
156
157
## Module and Bundle Mapping
158
159
### Module Declaration
160
161
Loadable components need to declare which modules they load for SSR support. This is typically automated by the Babel plugin.
162
163
```javascript { .api }
164
/**
165
* Manual module declaration (usually automated by Babel plugin)
166
*/
167
interface LoadableSSROptions {
168
/** Function returning webpack module IDs */
169
webpack?: () => number[];
170
/** Array of module paths */
171
modules?: string[];
172
}
173
```
174
175
**Usage Examples:**
176
177
```javascript
178
// Manual configuration (not recommended - use Babel plugin instead)
179
const LoadableComponent = Loadable({
180
loader: () => import('./MyComponent'),
181
loading: Loading,
182
webpack: () => [require.resolveWeak('./MyComponent')],
183
modules: ['./MyComponent'],
184
});
185
186
// With Babel plugin, this is automatically added:
187
const LoadableComponent = Loadable({
188
loader: () => import('./MyComponent'),
189
loading: Loading,
190
// webpack and modules options added automatically
191
});
192
```
193
194
### Bundle Information
195
196
Structure of bundle objects returned by `getBundles` function.
197
198
```javascript { .api }
199
/**
200
* Bundle information for including scripts in HTML
201
*/
202
interface Bundle {
203
/** Module ID or name */
204
id: number | string;
205
/** Module name from webpack */
206
name: string;
207
/** Filename of the bundle */
208
file: string;
209
/** Full public path to the bundle */
210
publicPath: string;
211
}
212
```
213
214
## Complete SSR Setup
215
216
### Server Setup
217
218
```javascript
219
import express from 'express';
220
import React from 'react';
221
import ReactDOMServer from 'react-dom/server';
222
import Loadable from 'react-loadable';
223
import { getBundles } from 'react-loadable/webpack';
224
import stats from './dist/react-loadable.json';
225
import App from './App';
226
227
const server = express();
228
229
// Serve static files
230
server.use('/dist', express.static('./dist'));
231
232
// SSR route
233
server.get('*', (req, res) => {
234
const modules = [];
235
236
const html = ReactDOMServer.renderToString(
237
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
238
<App url={req.url} />
239
</Loadable.Capture>
240
);
241
242
const bundles = getBundles(stats, modules);
243
244
res.send(`
245
<!DOCTYPE html>
246
<html>
247
<head>
248
<title>My App</title>
249
<meta charset="utf-8">
250
</head>
251
<body>
252
<div id="app">${html}</div>
253
<script src="/dist/manifest.js"></script>
254
${bundles.map(bundle =>
255
`<script src="/dist/${bundle.file}"></script>`
256
).join('\n')}
257
<script src="/dist/main.js"></script>
258
</body>
259
</html>
260
`);
261
});
262
263
// Start server after preloading
264
Loadable.preloadAll().then(() => {
265
server.listen(3000, () => {
266
console.log('Running on http://localhost:3000/');
267
});
268
});
269
```
270
271
### Client Setup
272
273
```javascript
274
// Client entry point (src/index.js)
275
import React from 'react';
276
import ReactDOM from 'react-dom';
277
import Loadable from 'react-loadable';
278
import App from './App';
279
280
// Preload components that were server-rendered
281
Loadable.preloadReady().then(() => {
282
ReactDOM.hydrate(<App />, document.getElementById('app'));
283
});
284
```
285
286
### Development vs Production
287
288
```javascript
289
// Development setup
290
if (process.env.NODE_ENV === 'development') {
291
// Skip preloading in development for faster startup
292
const app = express();
293
app.get('*', renderRoute);
294
app.listen(3000);
295
} else {
296
// Production setup with preloading
297
Loadable.preloadAll().then(() => {
298
const app = express();
299
app.get('*', renderRoute);
300
app.listen(3000);
301
});
302
}
303
```
304
305
## SSR Best Practices
306
307
### Component Definition
308
309
```javascript
310
// Good: Define loadable components at module level
311
const LoadableComponent = Loadable({
312
loader: () => import('./Component'),
313
loading: Loading,
314
});
315
316
export default function MyPage() {
317
return <LoadableComponent />;
318
}
319
320
// Bad: Define loadable components inside render methods
321
export default function MyPage() {
322
const LoadableComponent = Loadable({ // This won't work with preloadAll
323
loader: () => import('./Component'),
324
loading: Loading,
325
});
326
327
return <LoadableComponent />;
328
}
329
```
330
331
### Error Handling
332
333
```javascript
334
// Server error handling
335
Loadable.preloadAll()
336
.then(() => {
337
startServer();
338
})
339
.catch(err => {
340
console.error('Failed to preload components:', err);
341
process.exit(1);
342
});
343
344
// Client error handling
345
Loadable.preloadReady()
346
.then(() => {
347
ReactDOM.hydrate(<App />, document.getElementById('app'));
348
})
349
.catch(err => {
350
console.warn('Preload failed, falling back to render:', err);
351
ReactDOM.render(<App />, document.getElementById('app'));
352
});
353
```
354
355
### Multiple React Loadable Instances
356
357
Ensure only one instance of react-loadable is used in your application to avoid issues with `preloadAll()`.
358
359
```javascript
360
// package.json - ensure single version
361
{
362
"dependencies": {
363
"react-loadable": "5.5.0"
364
},
365
"resolutions": {
366
"react-loadable": "5.5.0"
367
}
368
}
369
```