Angular SSR - An Introduction to SSR


Today I want to introduce you to Angular SSR.

And some of the changes that we've seen to server-side rendering in Angular with the newest releases.

In earlier versions we used to use something called Angular Universal to render Angular first on the server and then on the client.

Angular Universal was a great start but it wasn't good enough.

That is why it was completely written and now we're using the new Angular SSR package.

3d-casual-life-man-woman-chatting-with-speech-bubble-and-web-browser-windows

Creating a SSR app with Angular

With the latest version of the Angular CLI we will be prompted if we want to enable SSR and client hydration when we create a new application.

We can also enable it as a parameter flag when creating the app.

ng new my-app --ssr

And that is how you can create a server-side rendering app with Angular.

But maybe you already created the app?

Adding SSR to an existing Angular project

With the new SSR package, we can add it to an existing Angular project like this.

ng add @angular/ssr

This will install the SSR package and it will create and configure all the files which are needed for server-side rendering.

How does the SSR set up work?

Let's look at what has changed.

When we enable SSR in Angular we have the normal Express project which will render our Angular application on the server and generate markup.

You can find this code inside the new server.ts file.

The most important part for us is around line 32 where we call the render function to render the HTML page and return it to the client.

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

Now let's take a look inside our main.server.ts file.

As you can see this file is extremely similar to the main.ts file for our client-side stuff.

We're using exactly the same bootstrap application - the only difference is that inside the main.server.ts file we're passing in an appConfig from app.config.ts file.

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

In our case we're rendering our main app.component.ts inside server-side first.

If we take a look at our app component we see just a completely normal standalone component.

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { UsersTableComponent } from './usersTable/usersTable.component';

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

But we should take a look at app.config.server.ts and app.config.ts.

One is what we use on client and the other is what we're using on the server.

The main difference that inside our server config we call provideServerRendering() and then it merges all options from our config with the server config.

This means that if we're adding some stuff to our app config we usually just add it to the app.config.ts and our config will be automatically merged.

import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';
import { provideHttpClient, withFetch } from '@angular/common/http';

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

export const config = mergeApplicationConfig(appConfig, serverConfig);

And that, my friend, is an introduction to server-side rendering in Angular.

Till later,

signature

Daniel Kreider - Angular Developer

Credits

Illustration by Icons 8 from Ouch!