written for coders favicon image

Angular Universal Server-side rendering (SSR) how and when to use it

The header image for the angular universal server-side rendering blog post

A deep dive into server-side rendering with Angular Universal, what is it, when to use it and how to implement it. These are the topics we will go through in great detail in this blog post. At the end of this article, you will exactly know what Angular Universal is, what it offers, how to optimize it, and when to use it in your applications.

Table of content:

Angular Universal, what is it?

Let's start by explaining what Angular Universal is and what it's used for. Traditionally websites got rendered on the server but with the rise of JavaScript, better browsers, and single-page application (SPA) frameworks this changed for a lot of applications.

With a regular Angular application (or any other SPA) our code is executed and rendered in the browser. We send our index HTML and some JavaScript modules to the browser where the code is executed, data requested, and used to render pages in the DOM based upon user actions and application state.

In some scenarios, you might not want this behavior but rather request the data and render the pages on the server before it's shipped to the browser. Angular Universal is the tool given to developers by the Angular framework to enable this functionality. It allows you to generate static pages rendered on the server that gets bootstrapped by our client. In simple terms, this generally means that our applications render more quickly and show the general layout of the website before it becomes fully interactive.

How does SSR with Angular Universal work?

Angular Universal will request the data needed to build the page and render the initial HTML and CSS before it's sent to the browser. Rendering the HTML pages can be done by pre-rendering the HTML page's during build time. This means you have a pre-render step during your application build that will generate the static HTML pages ahead of time and include them in your build output. You can also render the HTML pages server-side on the fly when it's requested by a user. This means you have no pre-rendered pages in your build output, but your server will request data and render the initial HTML and CSS when a user requests the (initial) page.

Angular Universal will not only ship the pre-rendered HTML and CSS to the browser but also a regular client-side Angular application. So that after the pre-rendered HTML is loaded your regular Angular application takes over again. This way the rest of the application is rendered client-side and we can still enjoy the behavior of a single-page application.

But now you might be asking, why use Angular Universal and server-side rendering when only our initial page load is rendered on the server and the rest gets rendered runtime and in the browser like any other Angular application?

Why use server-side rendering and Angular Universal?

Server-side rendering serves a few very specific purposes. If you are building an application where this is not a real factor or the difference is negligible you might consider building your Angular application without server-side rendering. Angular Universal and server-side rendering gives you:

  1. Better performance on mobile and low-powered devices

  2. The first contentful paint is faster with server-side rendering

  3. Server-side rendering improves the SEO of your application

Better performance on mobile and low-powered devices:

Some devices can't execute JavaScript code or have such bad support for it that the user experience is not working in actuality. Also, low-powered devices sometimes turn off support for JavaScript and slow down DOM rendering to preserve battery life. For these cases, it can be useful to have a server-rendered version of your application. Even though this might be a minimal version without JavaScript it might be the only valid way to approach your application on these devices.

The first contentful paint is faster with server-side rendering

Showing something fast when a user requests a page is critical for user engagement. Server-side rendering helps with a faster first paint on the screen, showing the user the website before it gets interactive with JavaScript code. We all experienced the "Loading..." on SPA applications, with Angular Universal and SSR you can show a full page of HTML directly when the user requests it. No loading placeholder anymore.

Basically, you are shipping a static landing page to the user until the rest of the application is loaded. Even with small differences in the load time and first paint of 100ms, you can notice a real change in the conversions and user interaction for your applications.

Server-side rendering improves the SEO of your application

SEO is crucial for a lot of websites. Basically for any blog, company website, or webshop, it's one of the most important things to focus on. Server-side rendering helps to make your SPA application SEO friendly. A regular SPA application mainly ships JavaScript code to the browsers. Web crawlers from search engines and social media websites like Google, Bing, Facebook, and Twitter cannot always index JavaScript code or navigate through your website like a human would do.

With server-side rendering, you are rendering all HTML content on the server giving the web crawlers complete HTML pages to work with and index. With a regular SPA, you have the risk that your pages are not indexed or found by web crawlers or are seen as empty pages with no content. Not only will server-side rendering ensure HTML content can be indexed, but it also allows for different Meta and Title tags on your HTML pages further improving the SEO of your application. We will dive a bit deeper into this later in the guide.

Let's start building our Angular Universal App

We start by creating a new Angular application with the Angular CLI.

ng new angular-universal-test-app

With this command, the Angular CLI will scaffold a new default Angular application for you. Open the newly created App in your preferred code editor. The result will look something like this:

Image of a new Angular application scaffolded by the Angular CLI

We will not be diving into the structure of the Angular application created by the CLI as that falls out of the scope of this guide. Let's move on by starting the application with the following command at the root of your project:

ng serve -o

The command will start the Angular development server and open a browser window on localhost:4200 with your Angular application running. You should see something like this:

Image of a default angular application created with the Angular CLI

Now let's inspect a few things to get a better idea of what is happening and what will be different when we use Angular Universal and server-side rendering. First, we open the DevTools from our browser by pressing F12. Now go to the network tab and make sure 'All' is selected in the filter bar. Refresh the page in the browser and you should see something like this in your DevTools:

Image of the DevTools when you reload the page of a default Angular application

Here you can see the files our Angular application fetches to load into the browser. The first line 'localhost' is our index.html file, and next the CSS and JS files that are needed to load the rest of our content. Since this is a regular SPA application without server-side rendering we provide the browser basically with an empty HTML file and the content is rendered by executing the JavaScript code we ship along with it to the browser. When you click on 'localhost' and click on the 'preview' tab you will just see an empty white page.

When you open the 'response' tab you will see the HTML shipped to the browser, it will look something like this:

Gist

As you can see this HTML does not reflect the page we see in our browser but rather an empty page with some script tags to include our JavaScript code that will render the page content. When web crawlers try to index your website this is what they see if they navigate to your pages. If web crawlers lack support for executing and indexing JavaScript content they will not know what to make of your page and just index it as an empty page excluding you from the search results.

Another thing we are missing for SEO purposes is a page title and description. When we search for something the search engine shows results in a format of a title and description. The values for this title and description are found in the meta and title tags inside the head of your HTML page. With a regular SPA application, these tags are injected and evaluated at runtime. Just as with the content a lot of search engine crawlers expect these tags to be there when the HTML page is loaded and do not index values injected at runtime.

When we use server-side rendering these HTML tags will be included in the HTML on the server before it's shipped to the browser. That way web crawlers can index it and show the correct title and descriptions for your indexed search results.

Let's move to the 'Performance insight' tab in the browser DevTools and click on "Measure page load"

Here we can clearly see that the browser already loaded our HTML, but first needs to execute the JavaScript code before it can render something on the screen. At 0.38 seconds the HTML is loaded but the preview window is still empty:

Now if we move forward in the timeline we can finally see the page in the preview window when we reach 1,3 seconds after the JavaScript code is executed and evaluated.

When the application is rendered on the server the HTML page will include the content and layout of the page giving the browser the ability to show something on the screen right after the HTML is loaded. Now it has to wait until the JavaScript code has been executed before it can draw something on the screen. When Angular applications get bigger and more complicated the page load times grow longer, with server-side rendering this can be improved quite a bit showing the first content full paint right after the HTML is loaded into the browser. Faster load times are a big part of a good user experience and will boost user interactivity and satisfaction for your applications.

Enough talk, let's continue to build!

As with most things Angular the heavy lifting is done for us and we can utilize the Angular CLI to convert our regular Angular application into an Angular Universal servicer-side rendered application. To convert your Angular app into an SSR app run the following command:

ng add @nguniversal/express-engine

Running the above command is basically everything you need to do to convert your Angular application into a server-side rendered application, but some extra information might be useful. The command added and update some files in your Angular application.

Image of the files added or updated by the Angular Universal command

First, let's have a look at our angular.json file. The angular.json is one of the files updated by the Angular CLI. It added a couple of build configurations and changed the output path for the build configuration. The newly added configurations are for your server configuration, development SSR server configuration, and the prerender configuration. A bit more information on this later in the guide. For the 'Build' configuration the output path is updated so your build output goes into a folder named browser. The new server build configuration outputs its files to a server folder inside your dist (next to the browser folder). The updated angular.json looks like this:

Gist

In your package.json a couple of new scripts and packages are added after running the CLI command. The important part is the newly added scripts. We will dive into more detail on when and how to use these new scripts in a bit.

The newly added scripts for SSR with Angular Universal

There are also a couple of files added by the CLI command, one of them is the entry point for our server application main.server.ts. It's similar to the entry point for the client-side rendered Angular app in main.ts, the difference being that we export the AppServerModule instead of the AppModule.

Gist

Let's have a look at the newly created AppServerModule. Again it's very similar to the AppModule. In fact, we import the AppModule into the AppServerModule so the AppServerModule has all the same components and modules as the AppModule with one addition the ServerModule from @angular/platform-server. This ServerModule will swap some internal Angular services mainly for rendering purposes but for the rest, everything is the same for your client-side and server-side rendered applications.

Gist

There are a couple more small changes made but the last important piece to the puzzle is the server.ts file added by the CLI command. This server.ts file contains the server that will render out HTML pages. It's a simple Express.js server that will render out HTML pages before sending them to the browser. The important part of the server is the ngExpressEngine() implementation. This function is a wrapper implementation around the render module from Angular Universal and it turns client requests into server-side rendered HTML pages.

Gist

Now that we have a better understanding of what the CLI command added and updated in our Angular application, let's have a look at how we can serve and build our SSR application and what the difference in the browser is. We can serve our SSR Angular application with a development server or with our own server.ts file. Let's first start it with the development server by running the following command:

npm run dev:ssr

This command will spin up a development server that will server-side render your HTML pages before sending them to the browser. This command will not make use of your own server.ts file and is not meant to use in production. The server will start on localhost:4200 similar to when you serve your regular SPA application and uses a development build of the application (no dist folder is created with a production build of your server and browser files).

When we open the DevTools in the browser and have a look at the 'localhost' request for the index.html we can see something different than before. Before we converted our Angular application into an SSR application we only saw a white screen when we inspected the preview tab, now we can see an actual preview of the page.

Preview of the loaded page by server-side rendering in Angular Universal

Also when we inspect the response tab in the DevTools we can see the HTML now reflects the page we see (and not an empty page like before). When a web crawler of a search engine now hits your page it will receive the full content of the page giving it the opportunity to index it correctly and include it in its search results.

When we head back over to the 'Performance insights' tab of the DevTools and hit 'Measure page load' again. Now we can clearly see that the browser is able to draw something on the screen right after it loaded the HTML page when it first needed to execute the JavaScript code before the first content was shown on the screen.

To server-side render other pages, there is nothing extra you have to do. Just add the routes and pages to your application and the server will pick them up, server-side render them, and provide it to the browser. Remember only the initially requested page is server-side rendered. After the application is loaded and the 'landing page' is rendered your regular SPA application takes over again.

You saw the difference in the browser and know how to serve our SSR Angular Universal app in a development environment, let's have a look at how we can use our server.ts and build our SSR application for a production environment.

First, we need to build our application using the following command:

npm run build:ssr

This command will build our client and server application and output it to a dist folder. Inside this dist folder, we find a browser folder containing our client-side code and a server folder containing our Express.js server for rendering our HTML pages.

Now to start our Angular Universal SSR application we run the following command:

npm run serve:ssr

This command will start your Express.js server on port 4000. If you now navigate to http://localhost:4000 in your browser you will see the Angular application again. This time served in production mode with our production server located in our dist folder.

Image of a default angular application created with the Angular CLI

Note that when you deploy your SSR application to Azure or some other hosting service it can not be hosted as a static website, but it needs to be hosted as a server application.

Prerndering static pages

The last command added to our package.json is the prerender command. With this command Angular Universal lets, you compile pages into pre-rendered static HTML pages at build time. Meaning you will already have the rendered HTML pages in your build drop-off in the browser folder of your dist. This is also known as static site generation.

The advantage of prerendering is that the HTML file doesn't have to be rendered on the server making it available to the browser slightly faster and allowing you to host it cheaper and more reliable with a CDN. It also gives you a bit more control over the HTML file since you have it in your build output. All this can contribute to an even better user experience and faster load times. A drawback might be that your build output gets larger and the build process takes a bit longer. You can prerender you complete (static) website and host it as is, or you can mix and match with your Angular universal server that sends the prerendered pages when they are available and otherwise renders the page on the server.

You can prerender pages by running the following command:

ng run <your-app-name>:prerender --routes /blog/1 /blog/2

The --routes parameter in the command can be omitted, in this case, the prerendering will take a lot more time and it might miss pages you wanted to be rendered. When you omit the --routes parameter Angular will guess routes and when it finds an existing one it gets prerendered. You can also provide routes with a file, if you have a lot of them you want to prerender this might be your best option. Just create a routes.txt file in the root of your project containing all your routes and run this command:

ng run <app-name>:prerender --routes-file routes.txt

For more information on prerendering, you can have a look at the Angular docs.

Improving our Angular Universal SSR application

We can still make a few improvements to our Angular Universal application so we do even better on the SEO front and offer a better user experience. Earlier we talked about the title tag and meta tags that need to be included in our HTML pages for SEO purposes. When we search for something in a search engine the results are displayed with a title and description. To tell the search engine what title and description to display we need to add a meta tag for the description and a title tag for the tile. Both HTML tags need to be included in the head tag f the HTML page.

Angular has a Title and Meta service we can use to set these values. Now that we are using server-side rendering with Angular Universal these HTML tags will be included in the HTML page before it's shipped to the browser letting web crawlers index the tags correctly even when there is no JavaScript support. The below file shows an example of how to use the Title and Meta service to include the Title and Meta tag into the HTML page.

Gist

Prevent double requests with Angular TransferState API

When we use Angular Universal and server-side rendering we introduce a new 'problem'. When we render the page on the server the requests needed to build the page are already made on the server, after we send the HTML to the front-end and the regular SPA takes over it will again make the request to fetch the data and repaint it. Often times this results in a flickering effect on the first-page load and this is not something we want or considered a good user experience.

What we need here is a mechanism that caches the requests made on the server and transfers this state to the regular SPA application when Angular Universal swaps them. This way no double requests are made. Lucky for us Angular provides us with a tool to do just that and it's called TransferSate.

The way it works is; you register a unique string value under which you save the cached values. This value is called the masterStateKey and each object you fetch should have its own masterStateKey under which it's saved. Next, you can use TranserState to get, set, or remove objects a bit like with localStorage.

Below is an example of how you can use TransferState. After you implement this the flickering will be gone and requests will not be made by the SPA on page load but only on the server side.

Gist

Conclusion

Angular Universal is a good way to improve your initial page load and overall SEO performance. Implementing server-side rendering is handled with a single command and maybe some improvements for an even better experience. If you are building a blog, webshop, or any other website where the page load times, the initial browser paints, and SEO is important adding Angular Universal and server-side rendering most likely is the way to go for you.

Make your friends more educated by sharing!