How To Use Angular Universal for Server Side Rendering

Updated on July 15, 2021

Seth Gwartney

How To Use Angular Universal for Server Side Rendering


Single-page Applications (SPAs) consist of a single HTML document that is served initially to a client. Any new views that are required in the app are generated solely on the client via JavaScript. The request-response cycle still happens, but this is usually only to RESTful APIs for data; or to get static resources, like images.

There are a lot of benefits to writing applications this way. However, there are some things that you lose, such as the ability for web crawlers to traverse your app and slower performance while the app is loading, which can take a significant amount of time. In comes Server-Side Rendering (SSR) to bridge the gap.

In this article, you will learn how to use Angular Universal for Server-Side Rendering.


To follow along with this tutorial, you will need:

This tutorial was verified with Node v16.4.2, npm v7.19.1, @angular/core v12.1.1, and @nguniversal/express-engine v12.1.0.

Step 1 — Getting Started with Angular Universal

An Angular application is a Single-page App - it runs in a client’s browser. Angular Universal, however, lets you also run your Angular app on the server. This enables you to serve static HTML to the client. With Angular Universal, the server will pre-render pages and show your users something, while the client-side app loads in the background. Then, once everything is ready client-side, it will seamlessly switch from showing the server-rendered pages to the client-side app. Your users shouldn’t notice a difference, beyond the fact that instead of waiting for your “Loading” spinner to finish, they at least can have some content to keep them engaged until they can start using the fully-featured client-side application.

SSR with Angular Universal requires changes in both the client application and the server stack to work. For this article, we’ll assume this is a brand new Angular application, barely created with the Angular CLI, at version >= 7. Just about any server technology can run a Universal app, but it has to be able to call a special function, renderModuleFactory(), provided by Angular Universal, which is itself a Node package; so serving this Angular application from a Node/Express server makes sense for this example.

We’ll be using a schematic to get us up and going quickly.

From your app directory open a terminal and run the following command:

  1. ng add @nguniversal/express-engine

You’ll notice this schematic made several changes to your app, modifying some files and adding some files:

Updates angular.json

  • projects.{{project-name}}.architect.build.options.outputPath changes to "dist/browser"
  • a new projects.{{project-name}}.architect is added, called "server"

This lets the Angular CLI know about our server/Universal version of the Angular application.

Updates package.json

Besides a few new dependencies (to be expected), we also get a few new scripts:

  • "dev:ssr"
  • "build:ssr"
  • "serve:ssr"
  • "prerender"

Updates main.ts

This has been modified so that the browser version of the app won’t start bootstrapping until the Universal-rendered pages have been fully loaded.

Updates app.module.ts

Modified to execute the static method .withServerTransition on the imported BrowserModule. This tells the browser version of the application that the client will be transitioning in from the server version at some point.

Creates server.ts

This is the NodeJS Express server. Obviously, you don’t have to use this exact server setup as generated, although make note of the line:

server.engine('html', ngExpressEngine({ ... }))

ngExpressEngine is a wrapper around renderModuleFactory which makes Universal work. If you don’t use this exact server.ts file for your set-up, at least copy this part out and integrate it into yours.

Creates tsconfig.server.json

This tells the Angular compiler where to find the entry module for the Universal application.

Creates app.server.module.ts

This is the root module for the server version only. You can see it imports our AppModule, as well as the ServerModule from @angular/platform-server, and bootstraps the same AppComponent as AppModule. AppServerModule is the entry point of the Universal application.

Creates main.server.ts

This new file basically only exports the AppServerModule, which is the entry point of the Universal version of the application. We’ll revisit this soon.

Step 2 — Starting Your Universal App

From a command line, run the following command:

  1. npm run build:ssr

And then run:

  1. npm run serve:ssr

Assuming you didn’t hit any snags during the build process, open your browser to http://localhost:4000 (or whatever port is configured for you), and you should see your Universal app in action! It won’t look any different, but the first page should load much quicker than your regular Angular application. If your app is small and simple, this might be hard to notice.

You can try throttling the network speed by opening Chrome Dev Tools, and under the Network tab, finding the dropdown that says Online. Select Slow 3G to mimic a device on a slow network - you should still see good performance for your landing page, and any routed pages you go to.

Also, try viewing the page source (right-click on the page and select View Page Source). You’ll see all normal HTML in the <body> tag that matches what is displayed on your page - meaning, your application can be meaningfully scraped by a web crawler. Compare this with the page source of a non-Universal application, and all you’ll see in the <body> tag is <app-root> (or whatever you’ve called the selector for your bootstrapped AppComponent).


In this article, you learned how to use Angular Universal for Server-Side Rendering.

Since a Universal application runs on the server and not in a browser, there are a few things you need to watch out for in your application code:

  • Check your use of browser-specific objects, such as window, document, or location. These don’t exist on the server. You shouldn’t be using these anyway; try using an injectable Angular abstraction, such as Document or Location. As a last resort, if you do truly need them, wrap their usage in a conditional statement, so that they’ll only be used by Angular on the browser. You can do this by importing the functions isPlatformBrowser and isPlatformServer from @angular/common, injecting the PLATFORM_ID token into your component, and running the imported functions to see whether you’re on the server or the browser.
  • If you use ElementRef to get a handle on an HTML element, don’t use the nativeElement to manipulate attributes on the element. Instead, inject Renderer2 and use one of the methods there.
  • Browser event handling won’t work. Your app won’t respond to click events or other browser events when running on the server. However, any link generated from a routerLink will work for navigation.
  • Avoid the use of setTimeout, where possible.
  • Make all URLs for server requests absolute. Requests for data from relative URLs will fail when running from the server, even if the server can handle relative URLs.
  • Similarly, security around HTTP requests issued from a server isn’t the same as those issued from a browser. Server requests may have different security requirements and features. You’ll have to handle security on these requests yourself.

Continue learning about what Angular Universal provides in the official documentation.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Seth Gwartney


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?

This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Is it possible to do this with java Spring Boot backend?

How do i deploy this SSR application to Digital Ocean server with ubuntu/nginx. The tutorial works well on local machine. What about production

Such a Great Article !!!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel