Server Side rendering with Angular 17

Updated : 03/01/2024 danny

The more well-known a website is, the higher its number of visitors will be.

Whether it is known or not will depend on its SEO .
If your site is written with javascript , SEO will not work.

To solve this problem you must use the concept of Server Side Rendering or SSR

In this tutorial we will create a web application with Angular version 17.0.8
​​​​​​​
Then we will apply the Server Side Rendering in this Web Application.

 

Server side Rendering avec Angular

How to do it ?

To start our project here is a summary of what we are going to do.

  • SEO and SSR Theory
    First of all we need to understand what SEO is.
    SEO, SEO and SSR, everything is explained in detail
  • Initializing the project from scratch
    We will use Angular CLI for setting up a project,
    Using best practices we will deploy the SSR.
  • Using a complex project
    We will use a complete project that we will adapt to use the SSR.
  • Project update
    Check used dependencies and update them.
  • Deployment
    How to deploy and test our application on the internet.
    ​​​​​​​
  • Source code
    The complete project code is available on Github.

Theory

What is the Internet?

  • Billions of humans
  • Millions of websites on a spider's web
  • And above all the way to bring them together

Let's start with humans
There are billions of them, they lead their lives and ask themselves billions of questions every day.

Angular , les humains et le web

The Internet is the well-known www .

Or World Wide Web literally the global spider web .
There are millions of websites that are supposed to answer all your questions.

Angular www et le seo

So it boils down to that.
Questions on one side, answers on the other.
​​​​​​​
We can already imagine supply and demand
​​​​​​​
To bring all these beautiful people together we will need intermediaries.

These will be the search engines or Search engine (SE in SEO!)

There are a lot of search engines.
But if we make a classification it gives this list

  • Google: 91.88%
  • Bing: 3.19%
  • Yandex: 1.52%
  • Yahoo! : 1.33%
  • Baidu: 0.76%
  • DuckDuckGo: 0.64%

This is called a hegemony.
So goes the world we will adapt.

​​​​​​​In any case the summary for our offer and our request will be this image.

angular et les moteurs de recherche

You would have understood it.
A well-known website is a site that has good SEO.
​​​​​​And above all who has good SEO with Google. CQFD and no choice.


Javascript and SEO

If you are a developer or want to become one, you will need to use tools to build a website.

Currently 3 tools stand out for creating a site.

  • Angular
  • React
  • Vuejs

These are tools that use Javascript language.
And the problem is that Javascript is the problem.

Angular, Javascript et le référencement

If you are using Javascript the google SEO will not work.
I will explain later in this tutorial why in detail.

So we have to find a solution.
In the case of Angular the solution is called Server Side Rendering or SSR.
And we will use a dedicated tool for this Angular Universal.

Now let's get down to business.


What are we going to do ?


I offer you two possibilities

  • Creating an SSR project from scratch
  • Using an existing project and adding the SSR

Let's start with the quickest but least detailed solution.


Creating a From Scratch application

If you are in a hurry, go further.
I give you the commands that allow you to obtain an SSR application in a few minutes

​​​​​​​I admit that it's a little brief but it has the merit of working (and rather well in fact).

​​​​​​​If you want more explanations on what was done I advise you to move on and use an existing project.

# Uninstall Angular CLI (in case an old version of Angular was installed)
npm uninstall -g @angular/cli

# Install Angular CLI specific version (latest if possible)
npm install -g @angular/cli@17.0.9

# Create a demo directory (the name here is arbitrary)
mkdir demo

# Go to this directory
cd demo

# Generate a project called angular-starter with manual choice of options (answer yes to everything)
ng new angular-starter

# Position yourself in the project
cd angular-starter

# Run the application in normal mode
npm run start

# Test the app in your browser
http://localhost:4200

# Installing the necessary dependencies for SSR
ng add @angular/ssr

# Build the application in SSR mode
npm run build

# Run the application in SSR mode
npm run serve:ssr:angular-starter

# Test the app in your browser
http://localhost:4000

What are we going to do ?


I offer you the second possibility for the most curious and the most patient.

​​​​​​​The base Angular project we will use already has the following features

  • Generated with Angular CLI
  • Routing
  • Lazy Loading
  • The Bootstrap CSS framework

All sources used are indicated at the end of the tutorial.

The final application is at the following address


Before you start

To be visited by a large number of users, a website must meet two essential conditions.

  • Show up as quickly as possible.
  • Be well referenced by search engines.

The technique that allows you to do this has a name.

  • Server Side Rendering in English.


We will apply this technique in an Angular project.
For this we will use the technology recommended by the Google teams

  • Angular Universal.

This technology will improve the natural referencing or SEO ( Search Engine Optimization ) of our site.


Creating the Angular project

To be able to continue this tutorial we must obviously have certain elements

  • Node.js : The javascript platform
  • Git : The version control software.
  • Angular CLI : The tool provided by Angular.
  • Visual Studio code : A code editor.

You can consult the following tutorial which explains in detail how to do it.


We will use an existing project

# Create a demo directory (the name here is arbitrary)
mkdir demo

# Go to this directory
cd demo

# Get the source code on your workstation
git clone https://github.com/ganatan/angular-modules.git

# Go to the directory that was created
cd angular-modules

# Run the installation of dependencies (or libraries)
npm install

# Run the program
npm run start

# Check how it works by running the command in your browser
http://localhost:4200/

Theory

Web pages generated with javascript frameworks use javascript.
Search engines currently have difficulty interpreting javascript.

We will verify this notion in a practical way.

We will run our application with the corresponding script.

# Running the application
npm run start

# Displaying the site in the browser
http://localhost:4200/

We will check the source code produced in the corresponding page.
Using the Chrome browser you have to type Ctrl + U to see the html code.

We notice that the “ Features ” code which is displayed in the browser does not appear in the code.

<!doctype html>
<html lang="en">

<head>
    <script type="module" src="/@vite/client"></script>

    <meta charset="utf-8">
    <title>AngularStarter</title>
    <base href="/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">

    <!-- Google tag (gtag.js) -->
    <script async="" src="https://www.googletagmanager.com/gtag/js?id=YOUR-ID"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag() { dataLayer.push(arguments); }
        gtag('js', new Date());

        gtag('config', 'YOUR-ID');
    </script>

    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <app-root></app-root>
    <script src="polyfills.js" type="module"></script>
    <script src="scripts.js" defer></script>
    <script src="main.js" type="module"></script>
</body>

</html>

By clicking on main.js we open this file which contains the text "Features"

Let's run a build with npm run build.
The dist/angular-starter directory contains the main.js file .
Let's open this file with our VS code editor, then do a search (Ctrl + F) for the text "Features".
It contains the text "Features".

The main.js file is a javascript file, so it will be misinterpreted by search engines.

We will see later in this tutorial that once SSR has been applied, the code appears directly in the HTML code and will thus be well interpreted by search engines.


Installation

The tool we will use to apply SSR to our project is

  • @angular/ssr version 17.0.9

The previous tool used until Angular version 16 was

  • Angular universal version 16.2.0


The latest version of this tool is available below

@angular/ssr allows generating static pages through a process called Server side rendering (SSR) .

The procedure to follow is detailed on the official Angular website.
https://angular.io/guide/ssr

We will use a simple CLI command

# Installation
ng add @angular/ssr

Angular universal

As a reminder, Angular CLI uses the principle of schematics via the ng add directive to modify our code and adapt it to the new functionality (here the ssr).

Many operations were performed automatically on our project.

If we had to carry out this operation manually here are the different steps that we would have had to follow.

  • Installing the new necessary dependencies
  • Editing the angular.json file
  • Creating the src/app/ app.config.server.ts file
  • Creating the src/ main.server.ts file
  • Creating the server.ts file
  • Editing the tsconfig.app.json file
  • Editing the src/app/ app.config.ts file
  • ​​​​​​​Editing the package.json file

Installation des dépendances.

# Install new dependencies in package.json
npm install --save @angular/platform-server
npm install --save @angular/ssr
npm install --save express
npm install --save @types/express
npm install --save @types/node

Editing the angular.json file.
Adding SSR-related properties

  • server
  • prerender
  • ​​​​​​​ssr
angular.json
          "builder": "@angular-devkit/build-angular:application",
          "options": {
            "outputPath": "dist/angular-starter",
            "index": "src/index.html",
            "browser": "src/main.ts",
            "polyfills": [
              "zone.js"
            ],
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "src/assets/params/css/fonts.googleapis.min.css",
              "src/styles.css"
            ],
            "scripts": [
              "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
            ],
            "server": "src/main.server.ts",
            "prerender": true,
            "ssr": {
              "entry": "server.ts"
            }
          },

Creating the app.config.server.ts file

src/app/app.config.server.ts
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering()
  ]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);

Creating the main.server.ts file

src/main.server.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';

const bootstrap = () => bootstrapApplication(AppComponent, config);

export default bootstrap;

Creating the server.ts file

The port used by default is 4000 we can change it if necessary in this file.

server.ts
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './src/main.server';

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const serverDistFolder = dirname(fileURLToPath(import.meta.url));
  const browserDistFolder = resolve(serverDistFolder, '../browser');
  const indexHtml = join(serverDistFolder, 'index.server.html');

  const commonEngine = new CommonEngine();

  server.set('view engine', 'html');
  server.set('views', browserDistFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.get('*.*', express.static(browserDistFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Angular engine
  server.get('*', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers } = req;

    commonEngine
      .render({
        bootstrap,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: browserDistFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

  return server;
}

function run(): void {
  const port = process.env['PORT'] || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

run();

Editing the file

  • tsconfig.app.json
tsconfig.app.json
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": [
      "node"
    ]
  },
  "files": [
    "src/main.ts",
    "src/main.server.ts",
    "server.ts"
  ],
  "include": [
    "src/**/*.d.ts"
  ]
}
src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes), provideClientHydration()]
};

Editing the package.json file

package.json
"scripts": {
    ...
    "serve:ssr:angular-starter": "node dist/angular-starter/server/server.mjs"
    ...
}

Update

We can take advantage of this to update the dependencies of the package.json file and adapt the version descriptors.

The following dependencies

  • @nguniversal/express-engine

Can be updated with versions

  • 17.0.9

The file will ultimately contain the following dependencies.

  "dependencies": {
    "@angular/animations": "17.0.8",
    "@angular/common": "17.0.8",
    "@angular/compiler": "17.0.8",
    "@angular/core": "17.0.8",
    "@angular/forms": "17.0.8",
    "@angular/platform-browser": "17.0.8",
    "@angular/platform-browser-dynamic": "17.0.8",
    "@angular/platform-server": "17.0.8",
    "@angular/router": "17.0.8",
    "@angular/ssr": "17.0.9",
    "@fortawesome/fontawesome-free": "6.5.1",
    "bootstrap": "5.3.2",
    "express": "4.18.2",
    "rxjs": "7.8.1",
    "tslib": "2.6.2",
    "zone.js": "0.14.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "17.0.9",
    "@angular-eslint/builder": "17.1.1",
    "@angular-eslint/eslint-plugin": "17.1.1",
    "@angular-eslint/eslint-plugin-template": "17.1.1",
    "@angular-eslint/schematics": "17.1.1",
    "@angular-eslint/template-parser": "17.1.1",
    "@angular/cli": "17.0.9",
    "@angular/compiler-cli": "17.0.8",
    "@types/express": "4.17.21",
    "@types/jasmine": "5.1.4",
    "@types/node": "20.10.6",
    "@typescript-eslint/eslint-plugin": "6.17.0",
    "@typescript-eslint/parser": "6.17.0",
    "eslint": "8.56.0",
    "jasmine-core": "5.1.1",
    "karma": "6.4.2",
    "karma-chrome-launcher": "3.2.0",
    "karma-coverage": "2.2.1",
    "karma-jasmine": "5.1.0",
    "karma-jasmine-html-reporter": "2.1.0",
    "typescript": "5.2.2"
  }

Debuggage

The application note code will cause an error during compilation.
So the code before after.
​​​​​​​It concerns the app.component.ts file

app.component.ts (before)
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink, RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterLink, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  title = 'angular-routing';
  footerUrl = 'https://www.ganatan.com';
  footerLink = 'www.ganatan.com';
  ngOnInit(): void {

    const navMain = document.getElementById('navbarCollapse');
    if (navMain) {
      navMain.onclick = function onClick() {
        if (navMain) {
          navMain.classList.remove("show");
        }
      }
    }
  }

}  
app.component.ts (after)
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink, RouterOutlet } from '@angular/router';

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

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterLink, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  title = 'angular-routing';
  footerUrl = 'https://www.ganatan.com';
  footerLink = 'www.ganatan.com';

  constructor(
    @Inject(PLATFORM_ID) private platformId: object) {
  }

  ngOnInit(): void {

    if (isPlatformBrowser(this.platformId)) {
      const navMain = document.getElementById('navbarCollapse');
      if (navMain) {
        navMain.onclick = function onClick() {
          if (navMain) {
            navMain.classList.remove("show");
          }
        }
      }
    }
  }

}  

Conclusion

All that remains is to test all the previous scripts and finalize with the SSR.

# Development
npm run start
http://localhost:4200/

# Tests
npm run test

# AOT Compilation
npm run build

# SSR Compilation
npm run build:ssr
npm run serve:ssr
http://localhost:4000/

Enfin nous allons vérifier le code source produit dans la page correspondante à la compilation SSR..
En utilsant le navigateur Chrome il faut taper Ctrl + U pour voir le code html.

On remarque que le code "Features" s'affiche cette fois dans le navigateur.
La page sera dès lors bien interprétée par les moteurs de recherche.

Noticed
Some versions of Angular 9 do not allow you to check the SSR result in your browser.
However SSR works on the server side with Google robots.

To check it use the curl software

Then check the contents of the ssr-results.txt file
You will see the desired text appear in the HTML code.

Other proof Use SEOQUAKE (SEO Toolbox) to check SEO on angular.ganatan.com/

I apply SSR on www.ganatan.com and angular.ganatan.com


Code source

The source code used at the start of the tutorial is available on github
https://github.com/ganatan/angular-react-modules

​​​​​​​The source code for this tutorial is available on GitHub.
Use git to grab this code and check how it works.

Simply go to the following address
https://github.com/ganatan/angular-ssr

And don't forget the P'tits Loups if you like the source code you know what you have to do.

A star on github can change a man

Un star sur github peut changer un homme

The following steps will allow you to obtain a prototype application


The last step provides an example application


The source code of this final application is available on GitHub
https://github.com/ganatan/angular-app