At its core, SSR (or server-side rendering) is a technology that renders web applications on the server instead of rendering them on the browser. Besides generating these static pages, Angular Universal – a library for SSR in Angular applications – also ensures that the app gets bootstrapped on the client browser.
- Gabriel Stellini
- April 25, 2022
- Reading time
- 6 Minutes
Feels overengineered - Why are we doing this?
SSRs help us fix both of these issues by making our pages individually indexable by web crawlers. They do this by converting the whole page into a static page (temporarily) before switching users over to the full app. 🎉 Server-side rendering also lowers the time to first contentful paint (FCP) and achieves a better SEO score due to faster page loads.
You may be asking yourself, why not just use a different technology altogether, like Wix or Squarespace, or build the app with vanilla JS/HTML/CSS? The main advantage here is that after the static page is loaded, the SPA is replaced in place of the static page, so you get all the nifty features of SPAs, with all the advantages of static pages! These include push notifications, route animations, and everything in between. 😁
Hold up – Are there alternatives to this?
Yes! You can also use Angular pre-rendering if you’re coming from the Angular sphere of influence. Pre-rendering is useful when the page’s content is not frequently changed, making it ideal for marketing sites or pages with static links. With prerendering, the pages are generated only at build time this makes them cheaper to run and maintain compared to SSRs. SSRs, on the other hand, render the page when a request hits our servers, which means that your content gets to stay dynamic. 💪
If you’re coming from the React world, there’s also Next.JS – a rising star that does mostly the same thing as Angular Universal. However, we’ll be focusing on Angular Universal for this article.
When should I not add it to my project?
SSRs should not be used when you only have static content on the site, simply because prerendering is a better fit. SSRs also need a caching layer in front of the server, which you’ll need to configure yourself to bust automatically when the dynamic content changes. Lastly, Angular Universal requires a Node.JS runtime on the server, so it is no longer an option when you can’t add this to your infrastructure (due to cost/security concerns).
Why did we go with Angular Universal?
One of the most significant factors was that Angular Universal has official support from the Angular team at Google, which has proven to be a fairly stable framework from Angular 2.0 until now (Angular 13 at the time of writing this article). This means it’s usually easy to find libraries that play well with SSR. Another bonus point is that updates are also relatively painless, and Universal has been around for quite some time (at least four years!).
How do I get started?
Simply run the following command to add Universal to your project:
ng add @nguniversal/express-engine
Then use the following command to start the dev server:
npm run dev:ssr
You should have a bunch of new files, and the changes in the angular/package.json are there to help you create a dev/production build. The other added files include the express server used for rendering the application on the server and several small architectural changes to the tsconfig (for the server itself).
Uhm…nothing changed in my app. Is it working?
To do this, simply use
Ctrl + Shift + P in your console to open the debugging tools and type
That’s cool and all, but what makes it all tick?
Here’s a short rundown of what happens from “start” to “finish”:
- The client browser sends a request for a page.
- The server (which runs on express by default) calls the renderModule() function.
- [Under the hood] This function uses the
platform-serverpackage, which provides server implementations of the DOM, XMLHttpRequest, and other low-level features that don't rely on a browser.
- [Under the hood] The function gives us back an
HTML Documentwhich we forward to the client – this document includes “de-hydrated” data (more on this in a bit).
- The data from this function is returned to the client.
- The client first renders the raw HTML, then “hot replaces” the document with the SPA itself. Any dehydrated data in the document script tag is used to hydrate the SPA and avoid additional API requests.
What’s the catch?
There are a few caveats when using Universal that you need to be aware of. Here’s what we have encountered thus far:
- Several objects like
locationare not usable on the server. However, you can use a special conditional check called
isPlatformBrowser(platformId)to check the environment you’re on before you do anything special with this.
- Most of these have their “server equivalent”, which works just as well.
- Libraries need to be SSR-compatible, or you need to exclude them during the server-side rendering process somehow. For example, if a library is doing fancy animations on the window object internally, it will break on the server.
- Unexpected bugs. Since the code is loaded twice, we need to ensure that some pages are not rendered on the server (or get rendered with dummy data).
- Consequently, guarding routes becomes complicated. For example, the login page is usually bypassed entirely when the client is signed in; however, a client is never signed in on the server.
- The page is always rendered on the server as soon as a request hits the express server. Without caching, your server will quickly get overwhelmed with requests.
- Routerlink doesn’t work in the pure HTML version of the app, which isn’t ideal for SEO. If links aren’t working, some search engines won’t know where links in the app should go, and therefore the app does not get indexed correctly. These links need to be manually added as hrefs and this can get very complicated if those links also reference Angular pages.
- Consequently, buttons and actions also won’t work until the full SPA is loaded. If this is important to you, you can use preboot to stage changes until after the app is done.
- Adding SSR does not automatically make the site rank well on Google.
- Other tooling like LHCI (pageview insights) and bundlephobia is also recommended in your development pipeline so that your site keeps ranking highly. As a bonus, this tooling can also be configured to notify you when SEO requirements change.
- You’ll probably also want to use a form of rehydration so that API requests don’t get sent twice – once by the server and once by the client. With the right implementation, you can even reduce the number of requests sent to the backend making your app cheaper to run!
Angular Universal is an awesome technology that allows us to make our Angular Apps indexable. Nowadays, many libraries are SSR friendly, which helps us significantly with development. It’s also super easy to try it out in your app, so give it a shot and let us know what you think!