Demystifying Angular 17 SSR: A Simple Guide to Understanding the Rendering Process
Tags: angular, webdev, javascript
Published: 2024-05-22 21:52:33

A 2018 PacketZoom study found that 63% of users abandon an app that takes more than 5 seconds to load, and 71% of users expect apps to load within 3 seconds. Server-side rendering (SSR) addresses these expectations by generating the initial HTML content on the server, leading to faster initial page loads and potentially reducing user abandonment rates. This means that when a user request a page, the server generates the HTML content for that page dynamically and sends it to the browser as a complete HTML document.

## How SSR Works

When a user requests a page, the server dynamically generates the HTML content for that page and dispatches it to the browser as a complete HTML document. This allows the browser to commence parsing and displaying the content even before fetching the requisite JavaScript for the application

Implementing SSR enhances performance, user experience, and search engine visibility, making it a valuable technique for web development.

## Why use SSR?
**Improved Performance:**

SSR can enhance the performance of web applications by delivering fully rendered HTML to the client. This means that the browser can start parsing and displaying the content even before it downloads the JavaScript required for the application. This is particularly beneficial for users on low-bandwidth connections or mobile devices.

**Improved Core Web Vitals:**

SSR often results in performance improvements that can be measured using Core Web Vitals metrics, such as reduced First Contentful Paint (FCP) and Largest Contentful Paint (LCP), as well as Cumulative Layout Shift (CLS).

**Better SEO:**

SSR can improve the search engine optimization (SEO) of web applications by making it easier for search engines to crawl and index the content of the application.

**Better User Experience:**

Users experience faster load times, resulting in a smoother and more engaging experience.

## Instances SSR might be beneficial:

**1. Content-heavy websites:**
Example: A news website that publishes numerous articles daily. Implementing SSR ensures that users can access content quickly, even on slower connections or devices.

**2. SEO optimization:**
Example: An e-commerce platform that wants its product pages to rank higher in search engine results. By using SSR, the platform ensures that search engines can crawl and index product information effectively.

**3. Social sharing:**
Example: A blog platform where users frequently share articles on social media platforms like Facebook and Twitter. SSR ensures that when articles are shared, the correct title, description, and image are displayed, improving click-through rates.

**4. First-time user experience:**
Example: A travel website where users often land on specific destination pages through search results, their initial experience is crucial. SSR enables the website to load relevant content quickly, providing users with a positive first impression and encouraging further exploration.

However, using SSR bears certain drawbacks:

**1. Complexity and Overhead:** Implementing SSR adds complexity to development workflows and may require additional server resources, leading to increased maintenance overhead.

**2. Increased Initial Server Load:** Handling server-side rendering requests may strain server resources, particularly during periods of high traffic, leading to slower response times or server downtime.

**3. Limited Compatibility with Third-Party Libraries:** SSR may introduce compatibility challenges with certain third-party libraries or components that rely heavily on client-side rendering, requiring additional effort to ensure seamless integration.

## Instances SSR might not be beneficial:

**1. Highly Interactive Applications:**

SSR is less suitable for highly interactive applications with complex client-side logic, such as real-time gaming platforms or sophisticated web-based tools.

**2. Dynamic Content:**

Applications that heavily rely on dynamic content generation based on user interactions or data input may not benefit significantly from SSR. Since SSR generates HTML on the server before sending it to the client, it may not capture dynamic changes that occur after the initial render without additional client-side updates.

**_Example:_** A social media feed where content frequently updates based on user interactions (likes, comments, etc.)

**3. Resource-Intensive Applications:**

SSR can introduce performance overhead, especially for resource-intensive applications that require heavy server-side processing or complex data computations. In such cases, SSR may strain server resources and slow down the rendering process, negatively impacting user experience.

**_Example:_** A data visualization tool that processes large datasets and generates complex interactive charts or graphs. SSR might not be suitable because of the computational overhead involved in rendering these visualizations on the server.

## Enabling Server-side Rendering

**1. Creating a New Application with SSR:** To create a new Angular application with SSR, run the following command:

```
ng new app-name --ssr
```
**2. Adding SSR to an Existing Project:** If you already have an Angular project and want to add SSR to it, run:

```
ng add @angular/ssr 
```
After running either of the above commands, your project structure will be updated.

You’ll have a new file named **server.ts** at the root of your project. This file is for the application server.

Inside the **src** folder, there will be additional files:

**- app.config.server.ts:** This file is for server application 
  configuration.
**- main.server.ts:** This file handles the main server 
  application bootstrapping.

To verify it worked run the application and view the **Page Source**, you should now see that the typical empty **_<app-root></app-root>_** will now have content inside it that is accessible to search crawlers.

## Hydration
Angular hydration refers to the process of converting static HTML content generated by the server into dynamic Angular components on the client-side.

Hydration involves reusing the pre-rendered HTML from the server to reconstruct the DOM (Document Object Model) on the client-side.

Without hydration enabled, server side rendered Angular applications will destroy and re-render the application’s DOM, which may result in a visible UI flicker.

Hydration ensures that users experience a smooth transition from static server-rendered content to an interactive client-side application without any noticeable delays or inconsistencies.

Hydration process expects the same DOM tree structure in both the server and the client, a mismatch will cause problems in the hydration process

To make sure the hydration process is quick and efficient, aim to keep the initial server-rendered version as light as possible.

## Enabling Hydration

Hydration is enabled by default when you use SSR.

You can enable hydration by following the below steps:

**1. Import provideClientHydration:**

In your **app.config.ts** import **provideClientHydration** from **@angular/platform-browser**

```
import { provideClientHydration } from '@angular/platform-browser';
```
2. Add provideClientHydration to Providers:

```
export const appConfig: ApplicationConfig = {
  providers: [provideClientHydration()]
};
```
Code which utilizes browser-specific symbols such as **window**, **document**, **navigator**, or **location** should only be executed in the browser, not on the server. This can be enforced through the **afterRender** (triggers after every change detection cycle) and **afterNextRender** (runs once and also after the change detection) lifecycle hooks which are only executed on the browser. These hooks can be called in any injection context, such as the constructor of a component or a service, as demonstrated in the example below:

```
import { Component, ElementRef, Input, ViewChild, afterNextRender, afterRender } from '@angular/core';
import { ProductService } from '../product.service';
import { Product } from '../product';
import { CommonModule } from '@angular/common';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-product-detail',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './product-detail.component.html',
  styleUrl: './product-detail.component.css'
})
export class ProductDetailComponent{
  @ViewChild('productDetail') productDetailRef!: ElementRef;

  @Input() set id(id: number) {
    this.product$ = this.productService.getProductById(id);    
  }

  product$!: Observable<Product | undefined>;

  constructor(private productService: ProductService) {
    afterRender(() => {
      const height = this.measureHeight();
      console.log('After Render height: ' + height + 'px');
    });

    afterNextRender(() => {
      const height = this.measureHeight();
      console.log('After Next Render height: ' + height + 'px');
    });
  }

  measureHeight() {
    const height = this.productDetailRef.nativeElement.offsetHeight;
    return height;
  }

}
```

## Skipping Hydration

Some components may not work properly with hydration enabled due to some of the following reasons:

**1. Direct DOM Manipulation:**

Components manipulating the DOM directly using native DOM APIs or methods like **innerHTML**, **outerHTML**, **appendChild**, etc., can cause a mismatch between expected and actual DOM structures during hydration.

**2. Invalid HTML Structure:**

Component templates with invalid HTML structure, such as \<table\> without a \<tbody\>, or nested \<a\> tags, may lead to hydration errors.

**3. Preserve Whitespaces Configuration:**

Inconsistent configuration or enabling **preserveWhitespaces** in **tsconfig.json** can cause issues during hydration. It’s recommended to keep this setting false for hydration to function properly. If this setting is not in your **tsconfig.json**, the value will be false and no changes are required.

**4. CDN Optimizations:**

CDN optimization features that strip out nodes considered unnecessary, including comment nodes, can disrupt hydration. Disabling such optimizations is necessary to ensure proper hydration.

**5. Custom or Noop Zone.js:**

Using custom or “noop” Zone.js implementations may lead to timing issues with Zone.js signals, affecting hydration timing events like onStable. Adjustments may be needed to ensure proper synchronization.

As a workaround you can skip hydration for particular components encountering issues. The following are the ways in which you can do that:

1. Add the **ngSkipHydration** attribute directly to the component’s tag:

```
<example-cmp ngSkipHydration></example-cmp>
```
2. Alternatively, you can set **ngSkipHydration** as a host binding in the component decorator. The host property allows you to define how your component interacts with its host element in the DOM

```
@Component({
  ...
  host: { ngSkipHydration: 'true' },
})
class ExampleCmp {}
```
The **ngSkipHydration** attribute can only be used on component host nodes.

Both of the approach will skip hydrating the entire component and its children.

However, keep in mind that by skipping hydration, the component will behave as if hydration is not enabled, resulting in the component being destroyed and re-rendered. Additionally, applying **ngSkipHydration** to the root application component would effectively disable hydration for the entire application

Remember, using **ngSkipHydration** should be a last resort. Components that break hydration should ideally be fixed rather than worked around.

## Optimization Techniques

**1. Caching for Efficiency**
When SSR is enabled, **HttpClient** responses are cached while running on the server.

**HttpClient** caches all **HEAD** and **GET** requests which don’t contain **Authorization** or **Proxy-Authorization** headers by default.

**HttpClient** checks whether it has data in the cache and if so, reuses it instead of making a new HTTP request during initial application rendering. **HttpClient** stops using the cache once an application becomes stable (meaning that the initial loading and rendering processes have completed) while running in a browser.

**POST** requests are not typically cached by default due to their dynamic nature, you have the option to include them in the caching mechanism using configuration options like **includePostRequests** when configuring **HttpClient** for SSR in your **app.config.ts** file.

```
providers: [
    provideClientHydration(
      withHttpTransferCacheOptions({
        includePostRequests: true,
      }),
    ),
  ]
```
**2. Lazy Loading**
Lazy load modules through implementation of deferrable views to reduce initial bundle size.

## Conclusion

Server-Side Rendering offers numerous benefits for web developers seeking to optimize performance, improve SEO, and enhance user experience. However, it’s essential to weigh these benefits against the potential challenges and complexities involved in implementing and maintaining SSR in a web application.

## CTA
Many developers and learners encounter tutorials that are either too complex or lacking in detail, making it challenging to absorb new information effectively.

[Subscribe to our newsletter](https://bytewave.substack.com/) today, to access our comprehensive guides and tutorials designed to simplify complex topics and guide you through practical applications.