Image of an arrow

How to Create an Angular Application with Server-Side Rendering?

kbarralon

Server-Side Rendering Management: An Angular’s Novelty Imposing a Challenge

Angular is a framework using the TypeScript Programming Language. Its 5th version (pentagonal-donut) was released in November 2017, containing new features and bugfixes. It is accompanied by the command line tool Angular CLI and new features such as a server-side rendering framework that has become very popular within the community of Angular users and developers.

However, one of the recurring problems that can be encountered when setting up a website with a modern library (e.g. Angular, React, Vue.js, etc.) is that, in general, web crawlers and robots will not be able to read that website! Therefore, if you want to share your article on Facebook, or simply be well referenced on search engines (i.e. SEO), this part may be complicated (although in the case of Google, it seems that their robots have recently learned to read pages generated by JavaScript).

The reason for this is simple: most robots do not know when your page will be entirely generated by JavaScript. Therefore, they will only be able to read a tiny part of your HTML – which is often a small ‘Loading …‘ message, and not very explicit! So, we are addressing this issue today in this post.

How To Create an Angular Application with Server-Side Rendering?

To overcome this problem we can use Angular CLI – the tool that helps to generate an Angular application from the command line. With its version 5 now available, it is now possible to manage a server state transfer to the client. For example, an application that makes a request to a REST API will not make the same request twice (i.e. on both server as well as client sides) while it can transfer the server-side result to the client. We will study these two cases successively in order to start an application with server-side rendering.

How to Initialize Your Angular Project?

Follow these steps in order to initialize your Angular project:

  • Start by installing the Angular CLI tool: npm i -g @angular/cli
  • Then, initialize the project: ng new ssr-app style = scss # Because it's better with SCSS.
  • Then, go to the directory of the generated application and launch the development server:
cd ssr-app
ng serve
  • Finally, visit http://localhost:4200/ and enjoy!

Now it’s time to configure the server-side rendering management.

How to Configure Server-Side Rendering?

Let’s start by installing the platform-server package: npm i -S @ angular / platform-server

In the file .angular-cli.json at the root of the project, you must add in the “apps” part, the following configuration:

{
   "name": "universal",
   "platform": "server",
   "root": "src",
   "outDir": "dist-server",
   "main": "main.server.ts",
   "tsconfig": "tsconfig.server.json",
   "environmentSource": "environments/environment.ts",
   "environments": {
      "dev": "environments/environment.ts",
      "prod": "environments/environment.prod.ts"
   }
}

In our src / app folder, we now need an app.server.module.ts file next to our app.module.ts file. This will be our entry point for our server-side application. Its content is:

import {NgModule} from '@angular/core';
import {ServerModule} from '@angular/platform-server';

import {AppModule} from './app.module';
import {AppComponent} from './app.component';

@NgModule({
   imports: [
      AppModule,
      ServerModule
   ],
   bootstrap: [AppComponent]
})
export class AppServerModule {
}

The file simply inherits modules from the app.module.ts file and adds the ServerModule module. As for the main.server.js file in the .angular-cli.json, we need to create it next to the main.ts file in the src / directory with the following content:

import {enableProdMode} from '@angular/core';
export {AppServerModule} from './app/app.server.module';

enableProdMode();

We also need to create a tsconfig.app.server.json file for TypeScript compilation next to tsconfig.app.json in the same directory:

{
   "extends": "./tsconfig.app.json",
   "compilerOptions": {
      "outDir": "../out-tsc/server",
      "module": "commonjs"
   },
   "exclude": [
      "test.ts",
      "**/*.spec.ts"
   ],
   "angularCompilerOptions": {
      "entryModule": "app/app.server.module#AppServerModule"
   }
}

In the src / app directory, we now need to modify the app.module.ts file by adding a method to our BrowserModule module:

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppComponent} from './app.component';

@NgModule({
   declarations: [
      AppComponent
   ],
   imports: [
      BrowserModule.withServerTransition({ appId: 'universal' })
   ],
   providers: [],
   bootstrap: [AppComponent]
})
export class AppModule {
}

We can now build our server side / client side application. To do so, add a line in the scripts of our package.json:

"scripts": {
   ...
   "universal": "ng build --prod && ng build --prod --app universal --output-hashing=none",
   ...
}

You may ask, what is this command for? It simply generates, on the one hand, a client-side build and, on the other hand, a server-side build (with reference to our app configured in the .angular-cli.json file). For the server side, we remove the creation of a hash on the file generated main.bundle.js for reference later.

If we run the npm run universal command, we get our two builds in two separate files, dist / and dist-server /.

From now on, we want to be able to launch our server. For now, the Angular Universal Package only supports Node.js and ASP.NET. We will write our server in Node.js.

We must first install the package @nguniversal/express-engine that will bootstrap our server Node.js with the Express framework. So just run the command npm i -S @nguniversal/express-engine.

We will then write a server.js file to the root of our project with this content:

require('zone.js/dist/zone-node');

const express = require('express');
const ngUniversal = require('@nguniversal/express-engine');

/**
* On charge le seul fichier de notre build côté serveur
*/
const bootstrap = require('./dist-server/main.bundle');

/**
* On crée motre instance Express
*/
const app = express();

app.get('/', (req, res) => res.render('index', {req, res}));

/**
* Obtient les fichiers statics provenant de la build dist/
*/
app.use(express.static(`${__dirname}/dist`));

/**
* Configuration de Express Engine
*/
app.engine('html', ngUniversal.ngExpressEngine({
   bootstrap: bootstrap.AppServerModuleNgFactory
}));
app.set('view engine', 'html');
app.set('views', 'dist');

/**
* Toutes les URLs renvoient à l'index.html du répertoire dist/
* Angular se charge se rediriger vers les bonnes routes
*/
app.get('*', (req, res) => res.render('index', {req, res}));

/**
* On lance notre serveur
*/
app.listen(3000, () => console.log(`Listening on http://localhost:3000`));

Time to celebrate! We have successfully configured our Angular application as well as our Node.js server.

Launch the server by typing ‘node server.js’ and go to the URL http://localhost:3000. Your page is server-side generated at launch, and then supported by the client-side build when loaded. If you disable JavaScript in your browser, your page is still generated because you have already properly configured it following these instructions.

How to Transfer the State of the Application?

Although our application works perfectly with this configuration, it is not completely optimal. If your application requires requests via a Rest API, for example, these will be done twice instead of once. Indeed, if the server is requested to make a request, the browser does not know it and will perform the same request a second time when the server side will take over.

For example, we can start by adding the HttpClientModule in our app.module.ts and our app.server.module.ts (the ideal would be to have a module file in common, but I leave you the freedom to do it!):

import {HttpClientModule} from '@angular/common/http';

@NgModule({
   ...
   imports: [
      ...
      HttpClientModule
   ],
   ...
})
export class AppModule {
}

Let’s go into our app.component.ts and delete the content we will replace with:

import {Component, OnInit} from '@angular/core';
import {HttpClient} from '@angular/common/http';

@Component({
   selector: 'app-root',
   templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
   image: any;

   constructor(private http: HttpClient) {
}

ngOnInit() {
   this.http.get('http://www.splashbase.co/api/v1/images/random')
      .subscribe(data => this.image = data);
    }
}

We will display a random image retrieved via a Rest API.

For rendering, let’s edit our app.component.html file that we will replace with this unique tag:

< img *ngIf="image" [src]="image.url" >

This image will be the result of our request to the API.

If we start a build with npm run universal and launch our server with ‘node server.js’, we have our page working correctly. The only downside is that the image appears a few milliseconds and is then replaced by another image. The reason is the same as before, a request is made server side, and another is made client side once the page loaded. The client-side query is therefore useless.

The idea here is to transfer the state of our request to the client side so as not to multiply unnecessary requests. To do so, we will import the ServerTransferStateModule in our app.server.module.ts and the BrowserTransferStateModule in our app.module.ts:

/* app.server.module.ts */
import {ServerTransferStateModule} from '@angular/platform-server';

@NgModule({
   imports: [
      ServerTransferStateModule /* Ici ! */
   ],
   bootstrap: [AppComponent],
})
export class AppServerModule {}

/* app.module.ts */
import {BrowserTransferStateModule} from '@angular/platform-browser'

@NgModule({
imports: [
   ...
   BrowserTransferStateModule /* Ici ! */
   ],
   bootstrap: [AppComponent],
})
export class AppServerModule {}

Once the configuration is done, we can go to our app.component.ts file in order to create a key before the declaration of our Class and to add TransferState in our constructor, such as the following:

import {makeStateKey, TransferState} from '@angular/platform-browser';

const IMAGE_KEY = makeStateKey('image');

@Component({
   selector: 'app-root',
   templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
   ...
   constructor(private http: HttpClient,
      private state: TransferState) {
   }
...
}

Then we modify our ngOnInit method in the following way:

ngOnInit() {
   const image: any = this.state.get(IMAGE_KEY, null);

   /**
   * Si l'image n'est pac contenue dans notre clef, on effectue la requête
   */
   if (!image) {
      this.http.get('http://www.splashbase.co/api/v1/images/random')
         .subscribe(data => {
            this.image = data;
            this.state.set(IMAGE_KEY, data as any);
          });
   }

   /**
   * Sinon, on récupère le contenu de notre clef
   */
   else {
      this.image = image;
   }
}

We then launch a build of our application as well as our server.

npm run universal
node server.js

Now, the content of the image is transferred from the server to the client and the request is made only once!

Conclusion

You are now well equipped with all you need to start an Angular application with server-side rendering configuration. As a precaution, you may now refrain from using all objects available in your browser unreservedly. For example, you may want to verify whether an object is client side or not. Consider the following example:

import { PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

   constructor(
      @Inject(PLATFORM_ID) private platformId: Object,
      @Inject(APP_ID) private appId: string) {
      if (isPlatformBrowser(platformId)) {
         console.log('Je suis appelé côté client !')
      }
    }

All these tips are meant to simplify your Angular developments with all the benefits of server-side rendering! Please stay tuned for more helpful tips…

  1. First thank you for the effort and work you put in this tutorial I really appreciate it! How ever there are some inconsistencies in the text and code.

    1. ‘”tsconfig”: “tsconfig.server.json”, in the .angular-cli.json’ but then when we create the file “We also need to create a tsconfig.app.server.json file for TypeScript” Which is it?
    2. ” As for the main.server.js file in the .angular-cli.json” is it a ts of a js File?
    I’m at the point where we run the server.js but I get an error “Cannot find module ‘@angular/compiler'”..

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.


Similar articles

Image of an arrow

Savoir-faire Linux participated in the tenth edition of DrupalCamp Montréal, which was held this year at Concordia University. It was the occasion to catch up with a good proportion of the Drupal developer community in Montreal, to exchange ideas with other companies that work with this technology, and to have an overview of how Drupal […]

26 Jun. 2018 Reading from 2 minutes.

When It Comes to Websites, Page Speed Matters! This article is motivated by our website project accomplished by our Integration Platforms and AI Department using Liferay 7 (the latest version of Liferay Portal) for one of our clients– a large Canadian corporation in telecommunications and media industry. Alexis, our front-end developer, shares with you his first-hand experience […]

12 Mar. 2018 Reading from 6 minutes.
Thumbnail image

Web Development Getting Started with Server-Side Rendering in Angular By Kévin Barralon This week we released a tutorial for developers using the Angular JavaScript framework to set them up with a pre-configured server-side rendering environment. This environment allows for the pre-rendering of an Angular site so that its contents can be made visible to robots […]

13 Dec. 2017 Reading from 3 minutes.
Thumbnail image

Design What Is Design System? By Patrick Bracquart For several years now, the complexity of the websites and applications has pushed us towards rethinking the concept of design, as well as the methodologies we use in the design process. This rethink is pushed by an ever-expanding field of necessary skills and positions in design such […]

7 Dec. 2017 Reading from 2 minutes.
Thumbnail image

PyCon 2017 (Part 2!) Talk: “Double Click: Continue Building Better CLIs” Summarized by Kévin Barralon This PyCon Canada talk was a presentation of Click, a Python package that allows you to create CLIs (Command-Line Interfaces) with only a few lines of code. Click is simple and easy to use, in that the configuration of commands […]

30 Nov. 2017 Reading from 6 minutes.
Close
Close