Server-side rendering in ASP.NET Core Angular

Pieterjan De Clippel
4 min readJul 16, 2019

--

There is an update on the story (angular 9): https://medium.com/@pieterjandeclippel/server-side-rendering-in-asp-net-core-angular-update-705c0ec01931

I’ve been trying to get Server-side rendering working in the Angular template for ASP.NET Core. I’m a developer, not a storyteller. So I’ll just jump right into it.

SSR is especially interesting for SEO purposes, or when visitors disable javascript, and you don’t want to limit the page for the user to the noscript tag.

Start by creating a new ASP.NET Core project. Choose the Angular template

You can already run the application. But when you look at the source code, you’ll see that there’s pretty much nothing on the page. The browser of the visitor renders the app. This is not good when SEO is important.

There is no content in the app-root tag

How do I add SSR

Go to the Solution Explorer and right-click the project file. Choose Edit project file from the context menu and set BuildServerSideRenderer to true:

Setting this flag will cause ASP.NET Core to trigger the ng build and ng build:ssr commands. When these commands succeed, a browser and server folder is generated in the ClientApp/dist folder.

Next, remove the ClientApp folder from the project and create a new project:

cd SpaPrerendering
ng new ClientApp --style=scss --routing

Add the express engine of Angular Universal to the project:

cd ClientApp
ng add @nguniversal/express-engine --clientProject ClientApp

This command generates the AppServerModule, main.server, tsconfig.server.json and tsconfig.app.json in the project.

Install the aspnet-prerendering node package:

npm install --save aspnet-prerendering

Add the SPA prerendering middleware. I removed the unnecessary parts of the code:

To make the angular app behave the same way for ASPNETCORE_ENVIRONMENT=Development or Production:

  • Open the ClientApp/angular.json file
  • Locate the projects:ClientApp:architect:build:options:outputPath property
  • Change the value from dist/browser to just dist
  • This will cause the app to behave the same regardless of whether ASPNETCORE_ENVIRONMENT is set to Development or Production

Select all 4 tsconfig files in the ClientApp folder, go to the properties window and modify the Build Action to Content.

Modify the following tsconfig files:

Add node to the types
Add node to the types and set module to commonjs

In case you’re using providers, modify the main.ts (ClientApp/src/main.ts):

Also modify the main.server.ts: (ClientApp/src/main.server.ts):

Now when you’d try to run the project, you’ll probably get the following error:

NodeInvocationException: Prerendering failed because of error: Error: Cannot find module SpaPrerendering\ClientApp\dist\server\main.js

This is because the app already starts to run before the Angular CLI server has finished building the app server-side. The ClientApp/dist/server folder simply doesn’t exist yet. Wait until the folder exists and run the project again.

And when we run again…

The final project is available on GitHub

Pass data from .NET to the Angular CLI during SSR

When you want to render a page that for example displays the information of a person, you’ll have to pass the person from ASP.NET to the angular app during the Server-side prerendering process.

Start by editing the Startup file:

Add the SupplyData command

Modify the main.ts. Provide some message value:

Modify the main.server.ts. Pass the value from the server to the provider:

Inject the message-provider in the AppComponent:

Now you can display the message on the AppComponent’s view:

You can pass any serializable object (like a person) to the SupplyData delegate, and use this object in the component’s view.

Result received from the server
Result after the browser loaded the Angular app

Errors and pitfalls

NodeInvocationException: Cannot destructure property `AppServerModule` of ‘undefined’ or ‘null’

Cause

  • Your tsconfig.server.json is incorrect: missing “compilerOptions:module” declaration
  • Your tsconfig.server.json is incorrect: missing “types” declaration

Possible solution

Open your tsconfig.server.json. Check if compilerOptions:module is set to commonjs. Check if compilerOptions:types is set to [ “node” ]:

NodeInvocationException: Prerendering failed because of error: Error: Cannot find module …

Cause:

  • You haven’t set the BuildServerRenderer flag in the project file
  • The server build hasn’t finished yet. Wait until the file ClientApp/dist/server/main.js exists
  • You misspelled the path to your server-main module

Solution:

  • Right click your project, choose Edit Project File and check if you have set <BuildServerSideRenderer>true</BuildServerSideRenderer>
  • Check if the file exists and sorta contains following first line `(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, (function(modules) {`. This is the file you need as BootModule.

NodeInvocationException: Prerendering failed because of error: TypeError: provideModuleMap is not a function

Cause:

  • You may have picked the wrong server.js file as BootModulePath.

Solution:

  • Ensure the BootModulePath is set to {spa.Options.SourcePath}/dist/server/main.js instead of {spa.Options.SourcePath}/dist/server.js

The prerendering build process did not complete within the timeout period of 0 seconds

  • This usually happens when Visual Studio is launched as administrator
  • Which will run the web application as administrator
  • Which when the node_modules folder doesn’t exist will run npm install as administrator, while the web application runs in the IISExpress user application pool

So the node_modules are only accessible to the administrator user, and the IISExpress user running the web application can’t access these files.

Solution is to remove the node_modules folder, and run npm install in a non-elevated cmd prompt.

--

--