How to Implement Angular Universal (SSR/SSG)
Tags: angularuniversal, angularssr, ssg, prerender
Published: 2023-08-28 19:06:17

---
title: How to Implement Angular Universal (SSR/SSG)
published: true
date: 2023-08-28 15:14:54 UTC
tags: angularuniversal,angularssr,ssg,prerender
canonical_url: https://medium.com/@seanhaddock_60973/how-to-implement-angular-universal-ssr-ssg-4e75852a3b28
---

![](https://cdn-images-1.medium.com/max/1024/0*s8fNcSDm25-hyVBj)
_Photo by Guillermo Ferla on Unsplash_

In my previous [post](https://dev.to/seanbh/csr-ssr-ssg-and-isr-in-angular-52o7) we took a look at CSR, SSR, SSG and ISR in Angular. In this post, I want to walk through how to setup Angular Universal to achieve SSR/SSG.

### The Starter App

This post is just about setting up Angular Universal, so we want to start with a completed application. We’ll use the standard **Tour of Heroes** app that you can download [here](https://angular.io/generated/zips/toh-pt6/toh-pt6.zip). If you want to follow along, download that app now.

Once downloaded, run:

```
npm i
ng serve -o
```

This will install dependencies and open a browser with the app running. Open the dev tools, go to the Network tab, and do two things to help us test:

- Check ‘Disable cache’
- Change ‘No Throttling’ to ‘Fast 3G’ to simulate a slower connection

Refresh the browser. In my test, it took ~20 seconds to load the app, during which time I was presented with a blank white screen. Note that this is the dev build which has not been optimized, and the production build will take significantly less time. But the increased time here helps to emphasize the point.

![](https://cdn-images-1.medium.com/max/1024/1*93YjY2nkaIAs7jtLSNcf1g.png)
_Screenshot of network tab_

The dashboard document, which is just the index.html file with the scripts injected, loaded in less than a second. But that document doesn’t display anything until all of the JavaScript has been loaded. Let’s see what happens when we add Angular Universal.

### Add Angular Universal

To add Angular Universal, stop the running app and execute:

```
ng add @nguniversal/express-engine
npm run dev:ssr
```

Note that I usually use PowerShell as my terminal but when I execute npm run dev:ssr and make changes to the file, I intermittently receive an error, and I’ve read that other people have the same issue. So for this reason I use a bash shell to run this command.

Navigate away from the app and then back to it. What is different? Now the page loads in less than a second rather than 20 seconds. Look at the network tab. Again, the dashboard document loads in under a second but now a full page is being delivered rather than just a shell.

But the page is not interactive at that point. We still have to wait for the JavaScript to load for it to be interactive.

### Adding Some Code to Help Visualize

Now let’s add some code to help us visualize what is going on.

Replace the contents of app.component.ts with this:

```
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import {
  Component,
  PLATFORM_ID,
  TransferState,
  inject,
  makeStateKey,
} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'Tour of Heroes';

  platformId = inject(PLATFORM_ID);
  transferState = inject(TransferState);

  browserTime?: string;
  serverTime?: string;

  ngOnInit() {
    const serverTimeStateKey = makeStateKey<string>('serverTime');

    if (isPlatformBrowser(this.platformId)) {
      // set the browser time now and every second after
      this.setBrowserTime();
      setInterval(() => this.setBrowserTime(), 1000);

      // set the serverTime from transfer state
      this.serverTime = this.transferState.get(
        serverTimeStateKey,
        "I don't know, I wasn't generated on the server."
      );
    } else if (isPlatformServer(this.platformId)) {
      // set the serverTime and put in transfer state for the browser to read
      this.serverTime = new Date().toLocaleTimeString('en-US');
      this.transferState.set(serverTimeStateKey, this.serverTime);

      console.log('I am being rendered on the server');
    }
  }

  setBrowserTime() {
    this.browserTime = new Date().toLocaleTimeString('en-US');
  }
}
```

When you use Angular Universal, components will _always_ be rendered in the browser but _sometimes_ they will _first_ be rendered on the server. Sometimes you have to know where the component is being rendered when you’re writing code and for that you use isPlatformBrowser and isPlatformServer.

What we are doing here is simply setting the time of render for demonstration purposes. We set the time the component is rendered on the server and the time the component is rendered on the browser. We also log a message to the console to track when the component is being rendered on the server.

When the component is rendered in the browser, it replaces what was there previously. So in order to preserve the server render time, we have to use TransferState. With TransferStatewe can remember and transfer data between the server and browser renders.

Add the following in app.component.html, just above the router-outlet, so that we can see the browser and server times.

```
<div style="margin-top: 25px">
  Time from the
  <span style="font-weight: 900; color: red; margin-right: 15px">BROWSER:</span>
  {{ browserTime }}
</div>
<div style="margin-top: 25px">
  Time from the
  <span style="font-weight: 900; color: red; margin-right: 15px">SERVER:</span>
  {{ serverTime }}
</div>
```

Now refresh the page. The server time shows up as soon as the page loads and reflects the time when the full page was rendered on the server. The browser time however, does not appear until the JavaScript is loaded. It reflects the time the page was rendered by JavaScript in the browser. Now that we have JavaScript, we can continually update it like a clock.

What happens if we refresh the page? Will the server time update or stay the same? The server time updates because every time you refresh, the full page is regenerated on the server:

![](https://cdn-images-1.medium.com/max/832/1*vy5VHSm3OV9AUu08UH8yfA.png)
_Screenshot showing browser and server times_

Every time you refresh the page, you will observe the same behavior. But note that as you navigate to different routes, there is no console message and no server activity (aside from the favorite icon and that’s only because we have disabled the cache). Once the JavaScript loads, the application is a SPA just like normal. Click the ‘Heroes’ button to navigate to the heroes route to see for yourself.

But if you enter the path to a different route in the address bar, it will request the fully rendered page for that route from the server. Enter: [http://localhost:4200/heroes](http://localhost:4200/heroes) to confirm:

![](https://cdn-images-1.medium.com/max/930/1*UGxxAE1zNzmYP23mElKgXQ.png)
_Screenshot showing console message that component was rendered on the server_

### Prerendering

Angular Universal comes with prerendering built-in. Prerendering means generating full static HTML pages for routes at _build_ time. This reduces the initial load time even more since the browser does not have to wait for the server to generate the page when it requests it.

The downside is that in order to update the page you have to do another build. And of course, if you need data from the request in order to generate the page (like a userId) prerendering would not be an option.

By default, Angular Universal will try to guess all of the non-dynamic routes and generate pages for them. But you can turn this off and instead specify routes explicitly in a route.txt file or in the angular.json file. This is essentially hybrid SSR/SSG.

To prerender, stop the running app and execute:

```
npm run prerender
```

Note that the console message telling us that the component is being rendered on the server displayed three times — once for each route (/, /dashboard, /heroes) .

![](https://cdn-images-1.medium.com/max/943/1*CdaxsAa2MF_MQj0rciBvfA.png)
_Console message showing three routes prerendered_

Take a look in the dist/angular.io-example/browser folder and you will see three index.html files — again, one for each route:

![](https://cdn-images-1.medium.com/max/345/1*CsbA5AFlrD5ki7Yc09I9GQ.png)
_Screenshot showing 3 HTML files_

Our browser/server time display is in the app component and so the HTML displaying the times got duplicated on all three pages:

![](https://cdn-images-1.medium.com/max/1002/1*hH6gJP_ey4JBXjd41ps0Sw.png)
_Screenshot showing duplicated HTML_

Now execute:

```
npm run serve:ssr
```

The app is now running on port 4000, so navigate to [http://localhost:4000/](http://localhost:4000/). Note that we did not get a console message telling us that the component is being rendered on the server when the page was requested and delivered.

Now what will happen when we refresh the page? Will the server time update or remain the same? It remains the same. The page is only generated on the server once at build time so the server time does not update.

![](https://cdn-images-1.medium.com/max/597/1*HMSRcaGtQWkUHcL_w5GMnQ.png)
_Screenshot showing that the server time does not update when you refresh the page_

Now we have an Angular application where we can generate full HTML pages for routes at build time, which is essentially SSG.

### Potential Gotchas

Before we leave, I want to highlight one of the potential gotchas with SSG. If you click on a specific hero, that will take you to a dynamic detail route and there is no server activity since the app is behaving as a SPA. But if you enter one of those detail routes (e.g., [http://localhost:4000/detail/13](http://localhost:4000/detail/13)) in the address bar, the page will get generated on the server since that is a dynamic route that was not generated by default.

![](https://cdn-images-1.medium.com/max/603/1*VYdQM0jC0ZPrQ2GIm9Z-1A.png)
_Screenshot showing that the server time is updated if you request a detail route page_

Now click the ‘Dashboard’ button. The server time still shows as 9:05 but this is not the time that the ‘Dashboard’ page was prerendered on the server — it’s the time the detail route was generated on the server.

Now refresh the page. The server time reverts back to the correct prerendered value for the dashboard route.

![](https://cdn-images-1.medium.com/max/564/1*oLASEBjvA_J1_cPaV6WqNA.png)
_Screen shot showing that the server time reverts to the prerendered value on page refresh_

This trivial example highlights one of the challenges that come with SSG and that is data consistency. If you are implementing SSG for an application that has dynamic data, you have to be careful and really think through how you are retrieving and displaying data. This is one of the reasons that you generally do not want to implement SSG unless your application needs it.

Of course, it’s not all or nothing. You can choose SSG for static pages and SSR for pages that have dynamic data.

That’s it. In this post we walked through how to implement SSR and SSG for an Angular application. Hope you found this useful — happy coding!

### Bibliography

- [https://angular.io/guide/universal](https://angular.io/guide/universal)
- [https://angular.io/guide/prerendering](https://angular.io/guide/prerendering)
- [https://angular.io/api/core/TransferState](https://angular.io/api/core/TransferState)