Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit c44ceeb

Browse files
Enable server-side prerendering in React+Redux template
1 parent cf7a519 commit c44ceeb

File tree

10 files changed

+54
-22
lines changed

10 files changed

+54
-22
lines changed
File renamed without changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as React from 'react';
2+
import { Provider } from 'react-redux';
3+
import { renderToString } from 'react-dom/server';
4+
import { match, RouterContext } from 'react-router';
5+
import createMemoryHistory from 'history/lib/createMemoryHistory';
6+
import routes from './routes';
7+
import configureStore from './configureStore';
8+
9+
export default function (params: any): Promise<{ html: string }> {
10+
return new Promise<{ html: string, globals: { [key: string]: any } }>((resolve, reject) => {
11+
// Match the incoming request against the list of client-side routes
12+
match({ routes, location: params.location }, (error, redirectLocation, renderProps: any) => {
13+
if (error) {
14+
throw error;
15+
}
16+
17+
// Build an instance of the application
18+
const store = configureStore();
19+
const app = (
20+
<Provider store={ store }>
21+
<RouterContext {...renderProps} />
22+
</Provider>
23+
);
24+
25+
// Perform an initial render that will cause any async tasks (e.g., data access) to begin
26+
renderToString(app);
27+
28+
// Once the tasks are done, we can perform the final render
29+
// We also send the redux store state, so the client can continue execution where the server left off
30+
params.domainTasks.then(() => {
31+
resolve({
32+
html: renderToString(app),
33+
globals: { initialReduxState: store.getState() }
34+
});
35+
}, reject); // Also propagate any errors back into the host application
36+
});
37+
});
38+
}

templates/ReactReduxSpa/ClientApp/components/Home.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default class Home extends React.Component<any, void> {
1717
<li><strong>Webpack dev middleware</strong>. In development mode, there's no need to run the <code>webpack</code> build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.</li>
1818
<li><strong>Hot module replacement</strong>. In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, rebuilt CSS and React components will be injected directly into your running application, preserving its live state.</li>
1919
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and the <code>webpack</code> build tool produces minified static CSS and JavaScript files.</li>
20+
<li><strong>Server-side prerendering</strong>. To optimize startup time, your React application is first rendered on the server. The initial HTML and state is then transferred to the browser, where client-side code picks up where the server left off.</li>
2021
</ul>
2122
</div>;
2223
}

templates/ReactReduxSpa/ClientApp/configureStore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { typedToPlain } from 'redux-typed';
77
export default function configureStore(initialState?: Store.ApplicationState) {
88
// Build middleware. These are functions that can process the actions before they reach the store.
99
const thunk = (thunkModule as any).default; // Workaround for TypeScript not importing thunk module as expected
10-
const devToolsExtension = (window as any).devToolsExtension; // If devTools is installed, connect to it
10+
const windowIfDefined = typeof window === 'undefined' ? null : window as any;
11+
const devToolsExtension = windowIfDefined && windowIfDefined.devToolsExtension; // If devTools is installed, connect to it
1112
const createStoreWithMiddleware = compose(
1213
applyMiddleware(thunk, typedToPlain),
1314
devToolsExtension ? devToolsExtension() : f => f

templates/ReactReduxSpa/Views/Home/Index.cshtml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
ViewData["Title"] = "Home Page";
33
}
44

5-
<div id="react-app">Loading...</div>
5+
<div id="react-app" asp-prerender-module="ClientApp/boot-server"
6+
asp-prerender-webpack-config="webpack.config.js">Loading...</div>
67

78
@section scripts {
89
<script src="~/dist/main.js" asp-append-version="true"></script>

templates/ReactReduxSpa/Views/Shared/_Layout.cshtml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
<title>@ViewData["Title"] - WebApplicationBasic</title>
77

88
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" />
9-
<environment names="Staging,Production">
10-
<link rel="stylesheet" href="~/dist/site.css" asp-append-version="true" />
11-
</environment>
9+
<link rel="stylesheet" href="~/dist/site.css" asp-append-version="true" />
1210
</head>
1311
<body>
1412
@RenderBody()

templates/ReactReduxSpa/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
"react-router-redux": "^4.0.0",
3434
"redux": "^3.3.1",
3535
"redux-thunk": "^2.0.1",
36-
"redux-typed": "^1.0.0"
36+
"redux-typed": "^1.0.0",
37+
"require-from-string": "^1.1.0",
38+
"webpack-externals-plugin": "^1.0.0"
3739
}
3840
}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
11
module.exports = {
2-
devtool: 'inline-source-map',
3-
module: {
4-
loaders: [
5-
{ test: /\.css/, loader: 'style!css' }
6-
]
7-
}
2+
devtool: 'inline-source-map'
83
};

templates/ReactReduxSpa/webpack.config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
var path = require('path');
22
var webpack = require('webpack');
3+
var ExtractTextPlugin = require('extract-text-webpack-plugin');
34
var merge = require('extendify')({ isDeep: true, arrays: 'concat' });
45
var devConfig = require('./webpack.config.dev');
56
var prodConfig = require('./webpack.config.prod');
67
var isDevelopment = process.env.ASPNET_ENV === 'Development';
8+
var extractCSS = new ExtractTextPlugin('site.css');
79

810
module.exports = merge({
911
resolve: {
@@ -12,18 +14,20 @@ module.exports = merge({
1214
module: {
1315
loaders: [
1416
{ test: /\.ts(x?)$/, include: /ClientApp/, loader: 'babel-loader' },
15-
{ test: /\.ts(x?)$/, include: /ClientApp/, loader: 'ts-loader' }
17+
{ test: /\.ts(x?)$/, include: /ClientApp/, loader: 'ts-loader' },
18+
{ test: /\.css/, loader: extractCSS.extract(['css']) }
1619
]
1720
},
1821
entry: {
19-
main: ['./ClientApp/boot.tsx'],
22+
main: ['./ClientApp/boot-client.tsx'],
2023
},
2124
output: {
2225
path: path.join(__dirname, 'wwwroot', 'dist'),
2326
filename: '[name].js',
2427
publicPath: '/dist/'
2528
},
2629
plugins: [
30+
extractCSS,
2731
new webpack.DllReferencePlugin({
2832
context: __dirname,
2933
manifest: require('./wwwroot/dist/vendor-manifest.json')

templates/ReactReduxSpa/webpack.config.prod.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
var webpack = require('webpack');
2-
var ExtractTextPlugin = require('extract-text-webpack-plugin');
3-
var extractCSS = new ExtractTextPlugin('site.css');
42

53
module.exports = {
6-
module: {
7-
loaders: [
8-
{ test: /\.css/, loader: extractCSS.extract(['css']) },
9-
]
10-
},
114
plugins: [
12-
extractCSS,
135
new webpack.optimize.OccurenceOrderPlugin(),
146
new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }),
157
new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' })

0 commit comments

Comments
 (0)