SSR doesn't seem to be running #59

Closed
opened 2025-08-09 17:14:50 +00:00 by fergalmoran · 0 comments
Owner

Originally created by @JoshClose on 5/21/2018

I have SSR setup with react/redux. I used the VS template to create it and did a few modifications. I'm trying to get data passed from the server to the client via asp-prerender-data.

When the page loads window.initialReduxState doesn't exist. I'm trying to figure out what's possibly wrong in my server.js file by putting console.log statements in, but they're not getting rendered to the ASP.NET Core Web Server output, or on the command line if I try and use dotnet run instead. I tried putting some throw statements in server.js and the page renders completely normally too. This is making me believe the server.js file isn't even being ran.

How can I tell if SSR is happening and server.js is being executed?

webpack.config.js

const path = require("path");
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const merge = require("webpack-merge");

module.exports = (env) => {
	const isDevBuild = !(env && env.prod);

	// Configuration in common to both client-side and server-side bundles
	const sharedConfig = () => ({
		stats: { modules: false },
		resolve: { extensions: [".js", ".jsx"] },
		output: {
			filename: "[name].js",
			publicPath: "dist/" // Webpack dev middleware, if enabled, handles requests for this URL prefix
		},
		module: {
			rules: [{
				test: /\.jsx?$/,
				include: /ClientApp/,
				use: {
					loader: "babel-loader",
					options: {
						presets: ["env", "stage-2", "react"]
					}
				}
			}, {
				test: /\.(png|jpg|jpeg|gif|svg)$/,
				use: "url-loader?limit=25000"
			}]
		}
	});

	// Configuration for client-side bundle suitable for running in browsers
	const clientBundleOutputDir = "./wwwroot/dist";
	const clientBundleConfig = merge(sharedConfig(), {
		entry: { "client": "./ClientApp/client.js" },
		module: {
			rules: [{
				test: /\.s[ca]ss$/,
				use: ExtractTextPlugin.extract({
					use: [
						isDevBuild ? "css-loader" : "css-loader?minimize",
						"sass-loader"
					]
				})
			}, {
				test: /\.(png|jpg|jpeg|gif|svg)$/,
				use: "url-loader?limit=25000"
			}]
		},
		output: { path: path.join(__dirname, clientBundleOutputDir) },
		plugins: [
			new ExtractTextPlugin("site.css"),
			new webpack.DllReferencePlugin({
				context: __dirname,
				manifest: require("./wwwroot/dist/vendor-manifest.json")
			}),
			new webpack.DefinePlugin({
				"process.env": {
					"NODE_ENV": JSON.stringify(isDevBuild ? "development" : "production")
				}
			})
		].concat(isDevBuild ? [
			// Plugins that apply in development builds only
			new webpack.SourceMapDevToolPlugin({
				filename: "[file].map", // Remove this line if you prefer inline source maps
				moduleFilenameTemplate: path.relative(clientBundleOutputDir, "[resourcePath]") // Point sourcemap entries to the original file locations on disk
			})
		] : [
				// Plugins that apply in production builds only
				new webpack.optimize.UglifyJsPlugin()
			])
	});

	// Configuration for server-side (prerendering) bundle suitable for running in Node
	const serverBundleConfig = merge(sharedConfig(), {
		resolve: { mainFields: ["main"] },
		entry: { "server": "./ClientApp/server.js" },
		plugins: [
			new webpack.DllReferencePlugin({
				context: __dirname,
				manifest: require("./ClientApp/dist/vendor-manifest.json"),
				sourceType: "commonjs2",
				name: "./vendor"
			})
		],
		output: {
			libraryTarget: "commonjs",
			path: path.join(__dirname, "./ClientApp/dist")
		},
		target: "node",
		devtool: "inline-source-map"
	});

	return [clientBundleConfig, serverBundleConfig];
};

React.cshtml

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery antiforgery
@{
	ViewData["Title"] = "";
	var antiforgeryToken = antiforgery.GetTokens(Context);
	var siteRoot = Url.Content("~/");
}

<div id="root" asp-prerender-module="ClientApp/dist/server"
	 asp-prerender-data='new {
		AntiforgeryToken = antiforgeryToken,
		ClientData = ViewBag.ClientData,
		SiteRoot = siteRoot
	 }'>Loading...</div>

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

server.js

import * as React from "react";
import { Provider } from "react-redux";
import { renderToString } from "react-dom/server";
import { StaticRouter } from "react-router-dom";
import { replace } from "react-router-redux";
import { createMemoryHistory } from "history";
import { createServerRenderer } from "aspnet-prerendering";
import { routes } from "./routes";
import configureStore from "./configure-store";

export default createServerRenderer(params => {
	return new Promise((resolve, reject) => {

		console.log("*******************************************************************************************************************************************");

		// Prepare Redux store with in-memory history, and dispatch a navigation event
		// corresponding to the incoming URL
		const basename = params.baseUrl.substring(0, params.baseUrl.length - 1); // Remove trailing slash
		const urlAfterBasename = params.url.substring(basename.length);

		const initialState = {
			antiforgeryToken: "the antiforgery token", //params.data.antiforgeryToken,
			siteRoot: "the site root", //params.data.siteRoot,
			user: params.data.user || {}
		};
		console.log("initialState", initialState);
		const store = configureStore(createMemoryHistory(), initialState);
		store.dispatch(replace(urlAfterBasename));

		// Prepare an instance of the application and perform an inital render that will
		// cause any async tasks (e.g., data access) to begin
		const routerContext = {};
		const app = (
			<Provider store={store}>
				<StaticRouter basename={basename} context={routerContext} location={params.location.path} children={routes} />
			</Provider>
		);
		renderToString(app);

		// If there"s a redirection, just send this information back to the host application
		if (routerContext.url) {
			resolve({ redirectUrl: routerContext.url });
			return;
		}

		// Once any async tasks are done, we can perform the final render
		// We also send the redux store state, so the client can continue execution where the server left off
		params.domainTasks.then(() => {
			resolve({
				html: renderToString(app),
				globals: { initialReduxState: store.getState() }
			});
		}, reject); // Also propagate any errors back into the host application
	});
});

client.js

import "./css/site.scss";
import * as React from "react";
import { render } from "react-dom";
import { AppContainer } from "react-hot-loader";
import { Provider } from "react-redux";
import { ConnectedRouter } from "react-router-redux";
import { createBrowserHistory } from "history";

import configureStore from "./configure-store";
import routes from "./routes"
let Routes = routes;

// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName("base")[0].getAttribute("href");
const history = createBrowserHistory({ basename: baseUrl });

// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = window.initialReduxState;
const store = configureStore(history, initialState);

function renderApp() {
	// 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.
	render(
		<AppContainer>
			<Provider store={store}>
				<ConnectedRouter history={history}>
					<Routes />
				</ConnectedRouter>
			</Provider>
		</AppContainer>,
		document.getElementById("root")
	);
}

renderApp();

// Allow Hot Module Replacement
if (module.hot) {
	module.hot.accept("./routes", () => {
		Routes = require("./routes").default;
		renderApp();
	});
}

configure-store.js

import { createStore, applyMiddleware, compose, combineReducers } from "redux"
import thunk from "redux-thunk";
import { routerReducer, routerMiddleware } from "react-router-redux";
import { createLogger } from "redux-logger"

import reducers from "./reducers";

const logger = createLogger({
	collapsed: true
});

export default function configureStore(history, initialState) {
	// Build middleware. These are functions that can process the actions before they reach the store.
	const windowIfDefined = typeof window === "undefined" ? null : window;

	// If devTools is installed, connect to it
	const devToolsExtension = windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__;

	const middleware = [
		thunk,
		routerMiddleware(history)
	];
	if (process.env.NODE_ENV !== "production") {
		middleware.push(logger);
	}

	const createStoreWithMiddleware = compose(
		applyMiddleware(...middleware),
		devToolsExtension ? devToolsExtension() : (next) => next
	)(createStore);

	// Combine all reducers and instantiate the app-wide store instance
	const store = createStoreWithMiddleware(reducers, initialState);

	// Enable Webpack hot module replacement for reducers
	if (module.hot) {
		module.hot.accept("./reducers", () => {
			const newReducers = require("./reducers");
			store.replaceReducer(newReducers);
		});
	}

	return store;
}

startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MyWebApp.Data;
using MyWebApp.Models;
using MyWebApp.Services;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite;

namespace MyWebApp
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
			//services.Configure<MvcOptions>(options =>
			//{
			//	options.Filters.Add(new RequireHttpsAttribute());
			//});

			services.AddNodeServices(options =>
			{
				options.LaunchWithDebugging = true;
				options.DebuggingPort = 9229;
			});

			services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            // Add application services.
            services.AddTransient<IEmailSender, EmailSender>();

            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
			//app.UseRewriter(new RewriteOptions().AddRedirectToHttps());

			if (env.IsDevelopment())
            {
                //app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
				app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
				{
					HotModuleReplacement = true,
					ReactHotModuleReplacement = true
				});
			}
			else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");

				routes.MapSpaFallbackRoute(
					name: "spa-fallback",
					defaults: new { controller = "Home", action = "Index" });
			});
        }
    }
}
*Originally created by @JoshClose on 5/21/2018* I have SSR setup with react/redux. I used the VS template to create it and did a few modifications. I'm trying to get data passed from the server to the client via `asp-prerender-data`. When the page loads `window.initialReduxState` doesn't exist. I'm trying to figure out what's possibly wrong in my `server.js` file by putting `console.log` statements in, but they're not getting rendered to the `ASP.NET Core Web Server` output, or on the command line if I try and use `dotnet run` instead. I tried putting some `throw` statements in `server.js` and the page renders completely normally too. This is making me believe the `server.js` file isn't even being ran. How can I tell if SSR is happening and `server.js` is being executed? webpack.config.js ```js const path = require("path"); const webpack = require("webpack"); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const merge = require("webpack-merge"); module.exports = (env) => { const isDevBuild = !(env && env.prod); // Configuration in common to both client-side and server-side bundles const sharedConfig = () => ({ stats: { modules: false }, resolve: { extensions: [".js", ".jsx"] }, output: { filename: "[name].js", publicPath: "dist/" // Webpack dev middleware, if enabled, handles requests for this URL prefix }, module: { rules: [{ test: /\.jsx?$/, include: /ClientApp/, use: { loader: "babel-loader", options: { presets: ["env", "stage-2", "react"] } } }, { test: /\.(png|jpg|jpeg|gif|svg)$/, use: "url-loader?limit=25000" }] } }); // Configuration for client-side bundle suitable for running in browsers const clientBundleOutputDir = "./wwwroot/dist"; const clientBundleConfig = merge(sharedConfig(), { entry: { "client": "./ClientApp/client.js" }, module: { rules: [{ test: /\.s[ca]ss$/, use: ExtractTextPlugin.extract({ use: [ isDevBuild ? "css-loader" : "css-loader?minimize", "sass-loader" ] }) }, { test: /\.(png|jpg|jpeg|gif|svg)$/, use: "url-loader?limit=25000" }] }, output: { path: path.join(__dirname, clientBundleOutputDir) }, plugins: [ new ExtractTextPlugin("site.css"), new webpack.DllReferencePlugin({ context: __dirname, manifest: require("./wwwroot/dist/vendor-manifest.json") }), new webpack.DefinePlugin({ "process.env": { "NODE_ENV": JSON.stringify(isDevBuild ? "development" : "production") } }) ].concat(isDevBuild ? [ // Plugins that apply in development builds only new webpack.SourceMapDevToolPlugin({ filename: "[file].map", // Remove this line if you prefer inline source maps moduleFilenameTemplate: path.relative(clientBundleOutputDir, "[resourcePath]") // Point sourcemap entries to the original file locations on disk }) ] : [ // Plugins that apply in production builds only new webpack.optimize.UglifyJsPlugin() ]) }); // Configuration for server-side (prerendering) bundle suitable for running in Node const serverBundleConfig = merge(sharedConfig(), { resolve: { mainFields: ["main"] }, entry: { "server": "./ClientApp/server.js" }, plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: require("./ClientApp/dist/vendor-manifest.json"), sourceType: "commonjs2", name: "./vendor" }) ], output: { libraryTarget: "commonjs", path: path.join(__dirname, "./ClientApp/dist") }, target: "node", devtool: "inline-source-map" }); return [clientBundleConfig, serverBundleConfig]; }; ``` React.cshtml ```html @inject Microsoft.AspNetCore.Antiforgery.IAntiforgery antiforgery @{ ViewData["Title"] = ""; var antiforgeryToken = antiforgery.GetTokens(Context); var siteRoot = Url.Content("~/"); } <div id="root" asp-prerender-module="ClientApp/dist/server" asp-prerender-data='new { AntiforgeryToken = antiforgeryToken, ClientData = ViewBag.ClientData, SiteRoot = siteRoot }'>Loading...</div> @section scripts { <script src="~/dist/client.js" asp-append-version="true"></script> } ``` server.js ```js import * as React from "react"; import { Provider } from "react-redux"; import { renderToString } from "react-dom/server"; import { StaticRouter } from "react-router-dom"; import { replace } from "react-router-redux"; import { createMemoryHistory } from "history"; import { createServerRenderer } from "aspnet-prerendering"; import { routes } from "./routes"; import configureStore from "./configure-store"; export default createServerRenderer(params => { return new Promise((resolve, reject) => { console.log("*******************************************************************************************************************************************"); // Prepare Redux store with in-memory history, and dispatch a navigation event // corresponding to the incoming URL const basename = params.baseUrl.substring(0, params.baseUrl.length - 1); // Remove trailing slash const urlAfterBasename = params.url.substring(basename.length); const initialState = { antiforgeryToken: "the antiforgery token", //params.data.antiforgeryToken, siteRoot: "the site root", //params.data.siteRoot, user: params.data.user || {} }; console.log("initialState", initialState); const store = configureStore(createMemoryHistory(), initialState); store.dispatch(replace(urlAfterBasename)); // Prepare an instance of the application and perform an inital render that will // cause any async tasks (e.g., data access) to begin const routerContext = {}; const app = ( <Provider store={store}> <StaticRouter basename={basename} context={routerContext} location={params.location.path} children={routes} /> </Provider> ); renderToString(app); // If there"s a redirection, just send this information back to the host application if (routerContext.url) { resolve({ redirectUrl: routerContext.url }); return; } // Once any async tasks are done, we can perform the final render // We also send the redux store state, so the client can continue execution where the server left off params.domainTasks.then(() => { resolve({ html: renderToString(app), globals: { initialReduxState: store.getState() } }); }, reject); // Also propagate any errors back into the host application }); }); ``` client.js ```js import "./css/site.scss"; import * as React from "react"; import { render } from "react-dom"; import { AppContainer } from "react-hot-loader"; import { Provider } from "react-redux"; import { ConnectedRouter } from "react-router-redux"; import { createBrowserHistory } from "history"; import configureStore from "./configure-store"; import routes from "./routes" let Routes = routes; // Create browser history to use in the Redux store const baseUrl = document.getElementsByTagName("base")[0].getAttribute("href"); const history = createBrowserHistory({ basename: baseUrl }); // Get the application-wide store instance, prepopulating with state from the server where available. const initialState = window.initialReduxState; const store = configureStore(history, initialState); function renderApp() { // 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. render( <AppContainer> <Provider store={store}> <ConnectedRouter history={history}> <Routes /> </ConnectedRouter> </Provider> </AppContainer>, document.getElementById("root") ); } renderApp(); // Allow Hot Module Replacement if (module.hot) { module.hot.accept("./routes", () => { Routes = require("./routes").default; renderApp(); }); } ``` configure-store.js ```js import { createStore, applyMiddleware, compose, combineReducers } from "redux" import thunk from "redux-thunk"; import { routerReducer, routerMiddleware } from "react-router-redux"; import { createLogger } from "redux-logger" import reducers from "./reducers"; const logger = createLogger({ collapsed: true }); export default function configureStore(history, initialState) { // Build middleware. These are functions that can process the actions before they reach the store. const windowIfDefined = typeof window === "undefined" ? null : window; // If devTools is installed, connect to it const devToolsExtension = windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__; const middleware = [ thunk, routerMiddleware(history) ]; if (process.env.NODE_ENV !== "production") { middleware.push(logger); } const createStoreWithMiddleware = compose( applyMiddleware(...middleware), devToolsExtension ? devToolsExtension() : (next) => next )(createStore); // Combine all reducers and instantiate the app-wide store instance const store = createStoreWithMiddleware(reducers, initialState); // Enable Webpack hot module replacement for reducers if (module.hot) { module.hot.accept("./reducers", () => { const newReducers = require("./reducers"); store.replaceReducer(newReducers); }); } return store; } ``` startup.cs ```cs using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MyWebApp.Data; using MyWebApp.Models; using MyWebApp.Services; using Microsoft.AspNetCore.SpaServices.Webpack; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Rewrite; namespace MyWebApp { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //services.Configure<MvcOptions>(options => //{ // options.Filters.Add(new RequireHttpsAttribute()); //}); services.AddNodeServices(options => { options.LaunchWithDebugging = true; options.DebuggingPort = 9229; }); services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // Add application services. services.AddTransient<IEmailSender, EmailSender>(); services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //app.UseRewriter(new RewriteOptions().AddRedirectToHttps()); if (env.IsDevelopment()) { //app.UseBrowserLink(); app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true, ReactHotModuleReplacement = true }); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); routes.MapSpaFallbackRoute( name: "spa-fallback", defaults: new { controller = "Home", action = "Index" }); }); } } } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/JavaScriptServices#59
No description provided.