Angular Server Side Rendering

with Angular 17

Beate Fiß

Why Server Side Rendering?

Traditional web crawlers struggled to interpret the dynamic content generated by Angular applications

=> results in incomplete indexing and poor search engine rankings

SSR pre-renders the web page on the server and provides search engines static HTML content
that is easily crawlable and indexable

Angular Universal <-> Angular SSR

Until Angular 16 the package was called angular-universal.

Since Angular 17 it's called angular-ssr - included into the Angular CLI

SSR Activation in an Angular Project

Setup new Angular Project with SSR

npx -p @angular/cli@17 ng new --directory . –ssr
Even without this param -ssr user is asked during creation of the new project, if ssr should be activated/generated.

Add SSR to an existing angular project

npx -p @angular/cli@17 ng add @angular/ssr

Server Side Rendering Configuration

Filename folder description
server.ts root folder express server that is executed inside Node. Will render our application on the server and will generate markup
main.server.ts /src bootstraps application on server with server config app.config.server.ts
app.config.server.ts /src/app server config, merged with appConfig

server.ts

Express Server - listens to routes that should be rendered on server:

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;
}

app.config.server.ts

ApplicationConfig with Server Rendering Provider,
merges the app config with server config

import { appConfig } from './app.config';

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

export const config = mergeApplicationConfig(appConfig, serverConfig);

Client Side Rendering Configuration

Filename folder description
main.ts /src bootstraps application on client side with app.config.ts
app.config.ts /src/app app config for client side rendering

app.config.ts

ApplicationConfig with Client Hydration Provider

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

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

Client Hydration

Hydration includes things like:

  • reusing the server rendered DOM structures
  • persisting the application state
  • transferring application data that was retrieved already by the server

=> improves application performance by avoiding extra work to re-create DOM nodes

Caching

When SSR is enabled, HttpClient responses are cached while running on the server.

Caching is performed by default for all HEAD and GET requests.

Can also be activated on Post requests and can be filtered (e.g. to deactivate caching for several routes)

Server Compatible Components

Some browser APIs and capabilities might not be available on the server, e.g.

  • browser-specific global objects like window, document, location, ...
  • cookies and localStorage

To handle this angular-ssr provides

  • Property PLATFORM_ID
  • methods afterRender() and afterNextRender()

Also useful to perform additional rendering on client side when SSR is active.

PLATFORM_ID

Property PLATFORM_ID provides a flag if code is executed on server or in client

platformId = inject(PLATFORM_ID); // 'browser' or 'server'

afterRender() and afterNextRender()

Called after page is rendered in browser

export class WeatherComponent {
  weatherforcast: any;

  constructor(
      private weatherHandler: WeatherForCastService
  ) {
    //  Weather forecast shall NOT be rendered on server but on client
    afterNextRender(() => {
      this.weatherHandler.getWeatherforcast().subscribe((data) => {
        this.weatherforcast = data;
      });
    });
  }
}

Prerendering

With SSR activated architect builder prerender block is added to angular.json.
Call ng build - Angular CLI generated files to folder dist/<project-name>/browser.

"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:application",
    "options": {
      "prerender": {
        "discoverRoutes": false,
        "routesFile": "prerenderedRoutes.txt"
      },
    },

prerenderedRoutes.txt:

/products/1
/products/555

Lifecycle

When opening a route / loading a page the following steps are performed:

  • check if this route exists in the dist-browser folder; if yes this static html file is returned
  • then the server side rendering is performed and the generated page is delivered and shown in browser
  • at last the client side rendering is performed and if hydration is activated the changes are adapted to the available DOM tree in browser

Test Server Side Rendering

Test Client Side Rendering

Test Prerendered Pages

TODOs:

  1. Should we really use the express server or is it just for testing?
  2. How to deploy the express server?
  3. Does it make sense to pre-render pages or is caching good enough?
  4. IF YES -> Find a way how to stop server and client side rendering for pre-rendered routes / documents

Angular 17 SSR - Angular Server Side Rendering in a New Way (Monsterlessons Academy)
https://www.youtube.com/watch?v=4KH-TStaiGw

Angular 16 Server Side Rendering and Client Side Rendering - Angular Universal (Programming with Umair)
https://www.youtube.com/watch?v=lZoRAcoEFOw

Server-side rendering from Angular 17 Documentation
https://angular.io/guide/ssr#server-side-rendering

Prerendering from Angular 17 Documentation
https://angular.io/guide/prerendering

Pre rendering static pages with Angular 16 and Angular Universal
https://www.youtube.com/watch?v=vmOWJvm3apA

Tackling some of the key SEO challenges in Angular applications
https://blogs.halodoc.io/tackling-some-of-the-key-seo-challenges-in-angular-applications