Lesson 16-Angular Universal

Server-Side Rendering (SSR) Concepts

What is Server-Side Rendering (SSR)?

Server-Side Rendering (SSR) is a web rendering technique that executes JavaScript code on the server to generate a complete HTML page, which is then sent to the browser. Unlike traditional client-side rendering, where a skeletal HTML page with JavaScript is sent and rendered dynamically in the browser, SSR delivers fully rendered content. This approach enhances First Contentful Paint (FCP), improves Search Engine Optimization (SEO), and provides a better user experience.

Advantages of Angular SSR

  • Faster First Contentful Paint: The browser receives a complete HTML page, eliminating the need to wait for JavaScript to download and execute.
  • SEO-Friendly: Search engine crawlers can directly access page content, improving indexability.
  • Enhanced User Experience: Users in low-bandwidth environments see basic content instead of a blank page.

Enabling Angular SSR

Install Necessary Dependencies

Install Angular Universal and related dependencies:

ng add @nguniversal/express-engine

This installs the Express engine, configures the project, and generates files needed for SSR.

Build the SSR Application

Build the Angular application with:

npm run build:ssr

This generates two directories: dist/client (client-side code) and dist/server (server-side code).

Start the SSR Server

Launch the SSR development server:

npm run serve:ssr

This starts an Express server to handle SSR requests.

Practicing Angular SSR

Let’s explore Angular SSR implementation through a simple example.

Step 1: Create an Angular Project

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

Step 2: Install Angular Universal

ng add @nguniversal/express-engine

This updates project configurations and installs required dependencies.

Step 3: Modify AppModule

Ensure src/app/app.module.ts is ready for SSR:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF } from '@angular/common';
import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
import { ServerModule } from '@angular/platform-server';

@NgModule({
  imports: [
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
    RouterModule.forRoot([]),
    ServerModule, // Add ServerModule
  ],
  declarations: [AppComponent],
  providers: [{ provide: APP_BASE_HREF, useValue: '/' }],
  bootstrap: [AppComponent],
})
export class AppModule {}

Create a server-side module, typically named app.server.module.ts:

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [AppModule, ServerModule],
  bootstrap: [AppComponent],
  entryComponents: [AppComponent],
})
export class AppServerModule {}

Step 4: Build the SSR Application

Build the application:

npm run build:ssr

Step 5: Start the SSR Server

Launch the SSR server:

npm run serve:ssr

Visit http://localhost:4000 in your browser to view the SSR application.

Considerations

  • Code Splitting: Ensure routing supports code splitting to load modules on demand, reducing initial load time.
  • State Management: Manage state consistency between server and client during SSR.
  • Asynchronous Data Loading: Load data before component initialization in SSR using the ngOnInit lifecycle hook and asynchronous strategies.
  • SEO Optimization: While SSR aids SEO, ensure metadata (e.g., titles, descriptions) is correctly generated server-side.

Conclusion

Angular SSR provides powerful tools and APIs for server-side rendering, crucial for enhancing user experience, SEO, and performance. By following these steps, you can implement SSR in your projects and leverage its benefits. However, SSR introduces complexities like state management and asynchronous data loading, requiring careful design and implementation.

Using Angular Universal for SSR

Angular Universal is Angular’s official SSR solution, enabling server-side prerendering of Angular applications to deliver static HTML to clients, significantly improving FCP and SEO.

Initializing the Project

Create a new Angular project if you don’t have one:

ng new my-ssr-app
cd my-ssr-app

Installing Angular Universal

Add Angular Universal to the project using Angular CLI:

ng add @nguniversal/express-engine

This installs necessary packages, including @nguniversal/express-engine, @nguniversal/module-map-ngfactory-loader, and the browser version of zone.js.

Modifying Project Configuration

Angular Universal requires specific configurations. In angular.json, update the architect object’s build and serve configurations:

"configurations": {
  "production": {
    "fileReplacements": [
      {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.prod.ts"
      },
      {
        "replace": "src/index.html",
        "with": "src/index-ssr.html"
      }
    ],
    "optimization": true,
    "outputHashing": "all",
    "sourceMap": false,
    "extractCss": true,
    "namedChunks": false,
    "aot": true,
    "extractLicenses": true,
    "vendorChunk": false,
    "buildOptimizer": true,
    "budgets": [
      // ...
    ]
  }
}

This specifies index-ssr.html as the template for production mode, required for Angular Universal.

Creating Server-Side Code

Define the server-side module in src/app/app.server.module.ts:

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [AppModule, ServerModule],
  bootstrap: [AppComponent],
  entryComponents: [AppComponent]
})
export class AppServerModule {}

Configuring the Server

In src/main.server.ts, set up the Express server to use Angular Universal:

import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './app/app.server.module';
import { existsSync } from 'fs';

// The Express app is exported for use by serverless functions
export function app() {
  const server = express();

  const distFolder = join(process.cwd(), 'dist/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index.html';

  // Universal 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 });
  });

  return server;
}

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

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

// Ensure the server runs only when not requiring the bundle
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 './app/main';

This code sets up an Express server to render HTML pages using Angular Universal’s engine.

Building the SSR Version

Build the SSR version of the application:

ng build --prod
ng run my-ssr-app:server:production

This generates dist/server and dist/browser folders containing server-side and client-side code, respectively.

Running the SSR Server

Start the SSR server:

node dist/server/main.js

Your Angular application should now run with server-side rendering. Visit http://localhost:4000 to see the result.

Testing and Debugging

During development, frequently build and test the SSR version. Angular CLI provides a serve-ssr command to automatically build and start the SSR server:

ng run my-ssr-app:serve-ssr

This launches a development server that rebuilds on code changes.

Considerations

  • State Management: Ensure state synchronization between server and client. Angular’s NgZone can assist with this.
  • Asynchronous Data Loading: Load data before component initialization in SSR using the ngOnInit lifecycle hook and asynchronous strategies.
  • Error Handling: Implement robust error handling in SSR to prevent rendering failures due to errors.

Share your love