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 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';
- 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;
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