NashTech Blog

Angular Hydration with Server-Side Rendering (SSR)

Table of Contents
angular-hydration-with-SSR

In the modern web development landscape, performance and user experience are paramount. One of the most effective ways to achieve this is through Server-Side Rendering (SSR). Angular, with its robust framework, offers SSR capabilities via Angular Universal. However, a crucial part of optimizing SSR is hydration, which bridges the gap between server-rendered HTML and client-side Angular applications. In this blog, we’ll dive deep into Angular hydration with SSR, exploring its benefits, setup, and implementation.

If you wants to know about the Top Angular Libraries and Tools in 2024, you can refer here.

What is Server-Side Rendering (SSR)?

Server-side rendering (SSR) is the process of rendering web pages on the server instead of in the browser. This approach improves performance, especially for the initial load, by delivering fully rendered HTML to the client, reducing the time it takes for users to see content.

Benefits of SSR:

  • Improved Performance: Faster initial load times.
  • SEO Optimization: Better indexing by search engines.
  • Enhanced User Experience: Quick content delivery.

What is Hydration?

Hydration is the process of converting the server-rendered HTML into a fully interactive client-side application. When the Angular framework takes over the static HTML, it “hydrates” it by attaching event listeners, initializing components, and bringing the application to life.

Benefits of Hydration:

  • Smooth Transition: Ensures a seamless transition from static HTML to an interactive application.
  • Reduced Time to Interactive (TTI): Faster TTI improves user engagement.
  • Optimized Rendering: Leverages server-side rendering while benefiting from client-side interactivity.

Setting Up Angular SSR

Before diving into hydration, let’s set up Angular SSR using Angular Universal.

Step 1: Create a New Angular Application

If you don’t have an existing Angular application, create one using Angular CLI:

ng new angular-ssr-example
cd angular-ssr-example

Step 2: Add Angular Universal

Add Angular Universal to your project:

ng add @nguniversal/express-engine

This command sets up Angular Universal with an Express server, generating the necessary files and configurations.

Step 3: Update Server-Side Code

Navigate to the server.ts file to ensure it handles server-side rendering correctly:

import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';

import { AppServerModule } from './src/main.server';

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/angular-ssr-example/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });

  // Serve static files from /browser
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Universal engine
  server.get('*', (req, res) => {
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  });

  return server;
}

function run(): void {
  const port = process.env.PORT || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
declare const __non_webpack_require__: NodeRequire;

const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './src/main.server';

Step 4: Build and Serve

Build your application for SSR:

npm run build:ssr

Serve your application:

npm run serve:ssr

Your Angular application is now set up for SSR!

Implementing Hydration

With SSR set up, hydration is the next critical step to ensure that your server-rendered application becomes fully interactive on the client side.

Step 1: Enable Zone.js

Ensure zone.js is enabled in your Angular application, as it’s crucial for change detection. Typically, this is already included in Angular projects.

import 'zone.js/dist/zone-node';

Step 2: Optimize AppModule

Update AppModule to support both server and client environments:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'angular-ssr-example' }),
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 3: Handle Client-Side Bootstrapping

Update main.ts to handle client-side bootstrapping:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

document.addEventListener('DOMContentLoaded', () => {
  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));
});

Step 4: Use Transfer State for Improved Performance

To enhance hydration performance, use Angular’s Transfer State feature to transfer data from the server to the client, avoiding duplicate HTTP requests.

Server-Side Transfer State

In your server-side code, add data to the transfer state:

import { TransferState, makeStateKey } from '@angular/platform-browser';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Injectable, Inject } from '@angular/core';

const STATE_KEY = makeStateKey<any>('stateKey');

@Injectable()
export class StateService {
  constructor(private transferState: TransferState, @Inject(REQUEST) private req: any) {
    if (this.req) {
      this.transferState.set(STATE_KEY, { data: 'Server-side data' });
    }
  }
}

Client-Side Transfer State

In your client-side code, retrieve the data from the transfer state:

import { TransferState, makeStateKey } from '@angular/platform-browser';
import { Injectable } from '@angular/core';

const STATE_KEY = makeStateKey<any>('stateKey');

@Injectable()
export class StateService {
  constructor(private transferState: TransferState) {
    const state = this.transferState.get(STATE_KEY, null);
    if (state) {
      console.log('Transferred State:', state);
    }
  }
}

Conclusion

Hydration is a crucial aspect of optimizing Angular applications with SSR. By setting up Angular Universal and implementing hydration, you can significantly improve the performance and user experience of your application. The combination of server-side rendering and client-side hydration ensures that your application is both fast and interactive, providing the best of both worlds.

Stay tuned for more advanced techniques and tips on optimizing your Angular applications. Happy coding!

Finally, for more such posts, please follow our LinkedIn page- FrontEnd Competency.

Picture of Alka Vats

Alka Vats

Alka Vats is a Software Consultant at Nashtech. She is passionate about web development. She is recognized as a good team player, a dedicated and responsible professional, and a technology enthusiast. She is a quick learner & curious to learn new technologies. Her hobbies include reading books, watching movies, and traveling.

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top