Editor’s note: This article was reviewed for accuracy by Oyinkansola Awosan on 15 May 2024 and updated to include information about Angular 17, expand on use cases and benefits of Angular SSR, discuss common issues when working with Angular SSR and their potential solutions, update the comparison with SSR in React, and add a comparison to SSR in Vue. Some of the information in this article may still be outdated with regard to the latest version of Angular.
Angular introduced non-destructive hydration for server-side rendering (SSR) in its v16 release in May 2023, marking a significant milestone for Angular developers. In the v17 release, non-destructive hydration became the default with the introduction of the new @angular ssr
package.
In this article, we will explore SSR and compare Angular’s SSR with React’s SSR. But before we delve into Angular SSR, let’s take a look at client-side rendering (CSR) and server-side rendering in the context of a single-page app (SPA).
In SPAs using CSR, the client app generates HTML within the browser using JavaScript. When the client app sends an initial request, the web server responds with a minimal HTML file to serve as the app container. The browser then downloads and executes the JavaScript bundlers referenced in the HTML file to bootstrap the app.
CSR has a few disadvantages, including:
SSR, however, addresses both the blank page issue and SEO concerns.
Using SSR, the HTML is generated on the server side, so the generated pages are fully formed and SEO-friendly. Its load time is also faster for the initial request, as the HTML is returned to the browser and displayed before JavaScript bundles are downloaded.
Thus, when SEO and initial load time are priorities, SSR is the recommended option.
In v16, Angular supports SSR through its Angular Universal package, a nickname for server-side rendering in Angular that refers to its ability to render at both the client and server sides.
First introduced in Angular 4, Angular Universal provides server-side rendering, including dynamic server rendering as well as static pre-rendering. However, this form of SSR had a few limitations due to its “destructive” nature.
To better understand how Angular Universal works, we need to look at the concept of hydration.
In v17, Angular supports SSR directly as Angular Universal is replaced by the @angular ssr
package. This package provides the core functionality for rendering Angular applications on the server, thus making Angular Universal outdated.
Angular hydration is the process of adding interactivity to a server-side rendered HTML page by adding event listeners and states. Efficient hydration is critical to user experience, but it is also challenging to implement as there are many moving parts to manage.
Before Angular 16, the hydration process in Angular Universal was destructive hydration. When a pre-Angular 16 app starts, the following events take place:
The above process is called destructive hydration because the client app discards the pre-rendered HTML and reloads the entire page.
It is worth noting that in step 3, the page displays some content known as the First Meaningful Paint (FMP). A quicker FMP is one of the primary benefits of SSR, particularly for performance-critical apps.
The problem of destructive hydration is page flickering, which happens when the server-side-rendered markup is replaced by the client-side rendered content. Multiple issues (i.e., 13446) have been opened in the Angular GitHub repo to resolve this, and it’s considered a main limitation of Angular Universal.
Angular 16 solves this problem by the introduction of non-destructive hydration.
In non-destructive hydration, existing server-side-rendered DOM markup is reused. That means the server-side-rendered DOM markup isn’t destroyed; instead, Angular will traverse through the DOM structure, attach the event listeners, and bind the data to complete rendering.
Below is a comparison diagram to illustrate the difference between destructive and non-destructive hydration:
Another related improvement is that HttpClient
has been updated to enable server-side request caching. This enhancement prevents redundant data retrieval on the client side by caching previously fetched data, resulting in better performance.
In Angular 16, Angular Universal provided more streamlined tooling support.
Assuming you have an existing Angular app, we just need to run the following command to enable server-side rendering:
ng add @nguniversal/express-engine
However, in Angular 17, Angular Universal was renamed to Angular SSR and is now built into Angular, effectively streamlining the process of enabling SSR in new and old projects. For existing projects, you only need to run the code below to enable SSR:
ng add @angular/ssr
This command will update your app to add a server-side application for SSR support and is unlike the Angular 16 update that enabled SSR using Angular Universal.
To enable non-destructive hydration in Angular 16, we need to import the provideClientHydration
function as the provider of AppModule
:
import {provideClientHydration} from '@angular/platform-browser'; // ... @NgModule({ // ... providers: [ provideClientHydration() ], // add this line bootstrap: [ AppComponent ] }) export class AppModule { // ... }
If you open package.json
, you will find a few new commands were added:
"dev:ssr": "ng run angularSSR:serve-ssr", "serve:ssr": "node dist/angularSSR/server/main.js", "build:ssr": "ng build && ng run angularSSR:server", "prerender": "ng run angularSSR:prerender"
To test server-side rendering in your local, run this command:
npm run dev:ssr
The app is running in SSR mode now!
It is important to note that in Angular 17, non-destructive hydration is a built-in feature, so there is no additional code specifically needed to enable it. It is the default behavior when using the @angular/ssr
package and building for SSR.
Partial hydration is when parts of the component tree are skipped during initial rendering, and hydrated later by user demand. This is in contrast to full hydration, where all the components in the page are rendered at once.
Partial hydration in Angular will be an exciting enhancement to the existing SSR approach, as it enhances performance and responsiveness to a greater extent.
To understand resumability, let’s look at the problem it tries to solve. In the hydration process, there is a period called the “Uncanny Valley”. As shown in the diagram below, the uncanny valley occurs when the page is rendered but is not interactive:
The page will become interactive after all of the event handlers and application states are restored for the whole component tree. In the uncanny valley period, if the user tries to click a button or scroll the page, nothing will happen.
Resumability resolves this issue by serializing event handlers and state on the server and resuming it on the client on demand.
For a resumable Angular app, if a user clicks on a button in the uncanny valley period, the button event handler will be downloaded directly and executed in the browser. It does this without needing to download all the JavaScript bundles and walk through the whole component tree.
Although resumability is a great improvement, it is a complex feature to implement and may introduce additional constraints for developer experience. The Angular team indicates that they are exploring the direction of leveraging Angular’s new Signals feature to provide a solution.
Angular SSR is becoming more modern and powerful. But how does it compare with React?
Angular’s non-destructive hydration works in a similar way to React SSR. React provides a server-side API to render React components to HTML on the server. The React client app will receive those HTML files and make the app interactive by attaching event handlers and states. This process is called React hydration.
The implementation of React SSR with pure React is a very manual process. You need to implement a server-side app, make changes to client-side components, and manage extensive configuration changes.
In contrast, the implementation of Angular SSR in Angular 17 and beyond is very straightforward, formally part of the Angular framework, and provides much more streamlined tools to support SSR.
Using Next.js makes it much easier to support SSR in React, just as using Nuxt.js makes SSR easier in Vue. Below is a summary of Angular’s SSR features compared with that of React and Vue.js:
Feature | Angular SSR | React + Next.js | Vue + Nuxt.js |
---|---|---|---|
Rendering strategy | Supports CSR, SSR, and SSG (or pre-rendering) | Supports CSR, SSR, and SSG (or pre-rendering) | Supports SSR, SSG, and CSR (or pre-rendering). |
Pre-rendering | Supports SSG for the whole or part of a site, as well as generating pages with dynamic routes | Supports SSG for the whole or part of a site, as well as generating pages with dynamic routes | Supports SSG for the whole or part of a site, as well as generating pages with dynamic routes |
Incremental static regeneration (ISR) | No out-of-the-box support, but offers a third-party library called ngx-isr | Supports ISR out of the box | Not offered or supported out-of-the-box. |
Mixing SSR and SSG | You need to write custom coded in server.ts to manage routes | Allows you to choose SSR or SSG on a page-by-page basis | Allows you to choose SSR or SSG on a page-by-page basis |
Ideal for… | ..large websites where high performance and SEO is a top priority as pre-rendering enables search engines to index content easily. Also great for applications with strong architectural and state management needs. |
..complex, dynamic SPAs where SEO, performance optimization is priortized as React is great for managing complex interfaces. Also great for projects where SEO, customization, and API integration is important. | ..content-heavy applications, e-commerce websites due to Nuxt.js support for generating static sites. Also great for projects with focus on SEO as Nuxt.js SSR ensures content is easily indexed. |
In summary, not only does Angular SSR offer great features similar to what Next.js + React and Vue + Nuxt.js offer, it’s also the only built-in one, making it a better option where SSR is concerned.
Angular 16 introduced many new features, including reactivity, SSR, and tooling updates.
Besides SSR, another heavyweight feature is signals, a new way to manage state changes that is much more intuitive and simple to use. Since signals allow developers to run change detection in specific components, they significantly improve runtime performance.
Other new features in the releases include:
DestroyRef
injectorAngular 16 made big improvements in application performance and UX. According to the Angular team, the Largest Contentful Paint (LCP) performance has improved by up to 45 percent using the enhanced SSR.
Angular 17 was released in December 2023 and it came with some amazing new features, like:
One of the top requests from the Angular community was for better SSR integration, and Angular 17 answered that. The team greatly improved SSR by introducing the new @angular/ssr
package, which simplifies SSR setup and integration into Angular projects.
Also, Angular‘s continued efforts to keep up with new TypeScript versions means better type checking, type safety, and overall experience when working with both tools.
While Angular’s SSR integration has seen many improvements, there are still some common issues that developers encounter. Let’s see an explanation of some of these issues and their potential solutions:
Some components may depend on data fetched through HTTP requests on the client-side, which means the data won’t be available on the server during SSR, leading to errors or incomplete rendering. Fetching data on the server-side can also lead to delays in page loading.
The solution here is to pre-fetch the data on the server before rendering. This results in faster page loads, reduces client-side requests and even allows the server to handle the initial request(s) better.
Hydration and rehydration errors happen when the HTML generated by the server doesn’t match what the client-side expects. This then leads to inconsistencies and can cause issues in making the page interactive.
To solve this issue, use browser developer tools to inspect the DOM and identify any discrepancies between the server-rendered HTML and the actual client-side structure. Also, ensure data is being fetched consistently to synchronize data between server and client, and verify browser compatibility and correct SSR configurations.
Lazy loaded modules may cause some delays in SSR if custom preloading strategies are not implemented correctly thus resulting in slower load time. Delays may also occur during initial loads if they are not preloaded, and a request needs to be made to the server.
There are various ways to go about a solution for this. Using resolvers which will load data only when needed may be helpful. It could also help to preload downloads and cache associated routes that may be needed.
Implementing proper preloading strategies and importing PreloadAllModules
can greatly help in preloading lazy-loaded routes and reducing load time, like in the example below:
import { NgModule } from '@angular/core'; import { RouterModule, Routes, PreloadAllModules } from '@angular/router';
While Angular 16 is a groundbreaking release, Angular 17 answers some urgent requests from the Angular developer community, making it an equally important release. The new SSR package makes integrations much easier, delivers significantly better performance, and sets the stage for further improvement.
As Angular continues to evolve, developers can look forward to a better development experience in 2024 and beyond.
Debugging Angular applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Angular state and actions for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site including network requests, JavaScript errors, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket NgRx plugin logs Angular state and actions to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.
Modernize how you debug your Angular apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
One Reply to "Server-side rendering in Angular 16 and beyond"
Nice share.