diff --git a/templates/ReactSpa/ClientApp/boot.tsx b/templates/ReactSpa/ClientApp/boot-client.tsx
similarity index 71%
rename from templates/ReactSpa/ClientApp/boot.tsx
rename to templates/ReactSpa/ClientApp/boot-client.tsx
index 3625bef..7d1e6bf 100644
--- a/templates/ReactSpa/ClientApp/boot.tsx
+++ b/templates/ReactSpa/ClientApp/boot-client.tsx
@@ -6,6 +6,8 @@ import * as ReactDOM from 'react-dom';
import { browserHistory, Router } from 'react-router';
import { routes } from './routes';
+// This code starts up the React app when it runs in a browser. It sets up the routing configuration
+// and injects the app into a DOM element.
ReactDOM.render(
,
document.getElementById('react-app')
diff --git a/templates/ReactSpa/ClientApp/boot-server.tsx b/templates/ReactSpa/ClientApp/boot-server.tsx
new file mode 100644
index 0000000..b433f2e
--- /dev/null
+++ b/templates/ReactSpa/ClientApp/boot-server.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react';
+import { renderToString } from 'react-dom/server';
+import { match, RouterContext } from 'react-router';
+import createMemoryHistory from 'history/lib/createMemoryHistory';
+import { routes } from './routes';
+
+// The 'asp-prerender-module' tag helper invokes the following function when the React app is to
+// be prerendered on the server. It runs asynchronously, and issues a callback with the React app's
+// initial HTML and any other state variables.
+
+export default function (params: any): Promise<{ html: string }> {
+ return new Promise<{ html: string, globals: { [key: string]: any } }>((resolve, reject) => {
+ // Match the incoming request against the list of client-side routes, and reject if there was no match
+ match({ routes, location: params.location }, (error, redirectLocation, renderProps: any) => {
+ if (error) {
+ throw error;
+ }
+
+ // Build an instance of the application and perform an initial render.
+ // This will cause any async tasks (e.g., data access) to begin.
+ const history = createMemoryHistory(params.url);
+ const app = ;
+ renderToString(app);
+
+ // Once the tasks are done, we can perform the final render
+ params.domainTasks.then(() => {
+ resolve({
+ html: renderToString(app),
+ globals: { /* Supply any other JSON-serializable data you want to make available on the client */ }
+ });
+ }, reject); // Also propagate any errors back into the host application
+ });
+ });
+}
diff --git a/templates/ReactSpa/Views/Home/Index.cshtml b/templates/ReactSpa/Views/Home/Index.cshtml
index 139ed8a..1bb189e 100644
--- a/templates/ReactSpa/Views/Home/Index.cshtml
+++ b/templates/ReactSpa/Views/Home/Index.cshtml
@@ -2,7 +2,7 @@
ViewData["Title"] = "Home Page";
}
-
Loading...
+Loading...
@section scripts {
diff --git a/templates/ReactSpa/Views/_ViewImports.cshtml b/templates/ReactSpa/Views/_ViewImports.cshtml
index 7252adc..3d6c9a3 100755
--- a/templates/ReactSpa/Views/_ViewImports.cshtml
+++ b/templates/ReactSpa/Views/_ViewImports.cshtml
@@ -1,2 +1,3 @@
@using WebApplicationBasic
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
+@addTagHelper "*, Microsoft.AspNet.SpaServices"
diff --git a/templates/ReactSpa/package.json b/templates/ReactSpa/package.json
index 283808b..3199313 100644
--- a/templates/ReactSpa/package.json
+++ b/templates/ReactSpa/package.json
@@ -13,6 +13,7 @@
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.5",
"jquery": "^2.2.1",
+ "ntypescript": "^1.201602232306.1",
"react-transform-hmr": "^1.0.2",
"style-loader": "^0.13.0",
"ts-loader": "^0.8.1",
@@ -24,6 +25,7 @@
},
"dependencies": {
"babel-core": "^6.5.2",
+ "domain-task": "^1.0.0",
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-router": "^2.0.0"
diff --git a/templates/ReactSpa/webpack.config.js b/templates/ReactSpa/webpack.config.js
index 44fbddd..503fea9 100644
--- a/templates/ReactSpa/webpack.config.js
+++ b/templates/ReactSpa/webpack.config.js
@@ -17,7 +17,7 @@ module.exports = merge({
]
},
entry: {
- main: ['./ClientApp/boot.tsx'],
+ main: ['./ClientApp/boot-client.tsx'],
vendor: ['bootstrap', 'bootstrap/dist/css/bootstrap.css', 'style-loader', 'jquery']
},
output: {