How to implement server-side rendering using Angular 4

Saumya Rastogi
Squareboat Blog
Published in
5 min readNov 16, 2017

As in my previous post about SPAs (Single Page Applications), I’ve mentioned that:

One of the biggest disadvantage of SPA is difficulty in the implementation of SEO (Search Engine Optimisation). The app’s page in the single page app never refreshes, since the data is fetched over API calls, thereby making it very hard for the page to be optimised for search engines.

It is very important to make a web-app which is super fast, has the capability to be grabbed by the search engines, and is readable by social media platforms to make sharing of web pages easy and interactive.

So to implement the SEO with SPAs one can use the technique — “server-side rendering” which basically means that the data which is been read by the search engine bots are directly been served by the server itself using Node.

Power of Search Engine Optimisation

SEO of Angular 4 applications

Why should one choose Angular 4 to implement SEO?

Angular CLI supports the universal build of an application. Its a CommonJS formatted bundle which is required into a Node application (e.g. an express server) and can be used with angular’splatform-server API to pre-render your application on the server itself.

Let’s see how you can implement it, step-by-step.

Installing required Dependencies (Prerequisites)

Before beginning with the process, install @angular/platform-server into the project and make sure that its version is similar to the version of @angular package.

The ts-loader package is needed to build your app using webpack and @nguniversal/module-map-ngfactory-loader package is also needed to handle lazy-loading in the context of server-render.

npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader

Step 1: Preparing app for Universal Rendering

The very first thing is to make your AppModule compatible with Universal using method .withServerTransition() and app’s ID to your BrowserModule import.

  • FILE: src/app/app.module.ts

Now, create a module which will be responsible for running on the server-side. Let’s call it AppServerModule .

  • FILE: src/app/app.server.module.ts

Step 2: Create ‘main’ file and ‘tsconfig’ to build it

Create a main file which is used by the Universal bundle. Make sure that this file is only exported by AppServerModule. Let’s call it main.server.ts .

  • FILE: src/main.server.ts

Create a new file, named tsconfig.server.json,which can be created by copying tsconfig.app.json file. Make sure to change its build to module target of commonjs.
Add a section for angularCompilerOptions and set entryModule to AppServerModule, specified as a path to the import appended with a hash # symbol, this would be app/app.server.module#AppServerModule.

  • FILE: src/tsconfig.server.json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
// Set the module format to "commonjs":
"module": "commonjs",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
],
// Add "angularCompilerOptions" with the AppServerModule you wrote
// set as the "entryModule".
"angularCompilerOptions": {
"entryModule": "app/app.server.module#AppServerModule"
}
}

Step 3: Add a new project in ‘.angular-cli.json’

Inside .angular-cli.json,there is an array with the key "apps”. Copy the configuration from your client’s application within that array, and paste it as a new entry in the same array with the additional key set to "server".
Keep in mind to remove "polyfills"key which is not required by the server. Finally, make sure to give a new location to the key "outDir".

  • FILE: ./.angular-cli.json
{
...
"apps": [
{
// Keep your original application config intact here, this is app 0
// -EXCEPT- for outDir, udpate it to dist/browser
"outDir": "dist/browser" // <-- update this
},
{
// This is your server app. It is app 1.
"platform": "server",
"root": "src",
// Build to dist/server instead of dist. This prevents
// client and server builds from overwriting each other.
"outDir": "dist/server",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
// Change the main file to point to your server main.
"main": "main.server.ts",
// Remove polyfills.
// "polyfills": "polyfills.ts",
"test": "test.ts",
// Change the tsconfig to point to your server config.
"tsconfig": "tsconfig.server.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
...
}

Creating the bundle

Now as we’re done with the server module, its time to build the server bundle for the application. Use --app flag to specifically tell the ‘angular-cli’ to build the server bundle, referencing its index of 1 in the apps array in .angular-cli.json.

# This builds the client application in dist/browser/
$ ng build --prod
...
# This builds the server bundle in dist/server/
$ ng build --prod --app 1 --output

Step 4: Setting up an Express Server to run our built Universal bundles.

There exists a method named renderModuleFactory(), offered by PlatformServer module from Angular. It can be used to pass in the AoT’s AppServerModule to serialize our app. The serialized version will then be returned to the browser at the end.

app.engine('html', (_, options, callback) => {
renderModuleFactory(AppServerModuleNgFactory, {
// Our index.html
document: template,
url: options.req.url,
// DI so that we can get lazy-loading to work differently (since we need it to just instantly render it)
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP)
]
}).then(html => {
callback(null, html);
});
});

Create a Typescript version of our express server module to make everything up & running.

Note: This is just for the sake of demonstration. In a real production environment, you’d want to make sure you have other authentication and security things setup here as well.

  • FILE: ./server.ts

Step 5: Setting up ‘webpack’ to handle server.ts to serve the app.

Now as we’re done with our express thing, its time to pack it up all together and finally serve it.

Create a file, named webpack.server.config.js, at the root level of the app.

This file basically takes that server.ts file, and compiles it for every dependency it has into dist/server.js.

  • FILE: ./webpack.server.config.js

Almost done!

So this is how our directory structure will look like after configuring the app for server-side rendering.

/dist/
/browser/
/server/

To fire up our integrated node app, type this in your terminal & hit enter:

node dist/server.js

Last but not the least, lets create few handy scripts inside package.json to make the thing go up & running with a single command like a champ.

"scripts": {

// These will be your common scripts
"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
"serve:ssr": "node dist/server.js",

// Helpers for the above scripts
"build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
"webpack:server": "webpack --config webpack.server.config.js --progress --colors"
}

From now on you’ll be able to start your app with the following command:

npm run build:ssr && npm run serve:ssr
At last you’ll find yourself doing this.. “I’ve done it…!”

Bonus

You can also use Angular’s Meta service to dynamically place meta tags on your app.

import { Meta }  from '@angular/platform-browser';constructor( private metaService: Meta ) {}this.metaService.addTags([
{ name: 'og:title', content: 'Title' },
{ name: 'og:description', content: 'Description' },
{ name: 'og:image', content: 'http://domain.com/img.jpg' },
]);

Server-side Rendering makes it easy to share your content over social media apps.

Sample view of static page source which is read by search engine bots

--

--