DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Server-side rendering (SSR) with Angular Universal
Balaji
Balaji

Posted on • Updated on

Server-side rendering (SSR) with Angular Universal

Angular Server-side rendering(SSR) with Angular Universal

Angular Universal, a technology that renders Angular applications on the server.

In this post we will walk through Server-side rendering(SSR) with Angular Universal step by step.

Installation

Use the package manager universal to install universal.

ng add @nguniversal/express-engine
Enter fullscreen mode Exit fullscreen mode

Updated Filles after installation

src/
  index.html                 app web page
  main.ts                    bootstrapper for client app
  main.server.ts             * bootstrapper for server app
  style.css                  styles for the app
  app/ ...                   application code
    app.server.module.ts     * server-side application module
server.ts                    * express web server
tsconfig.json                TypeScript base configuration
tsconfig.app.json            TypeScript browser application configuration
tsconfig.server.json         TypeScript server application configuration
tsconfig.spec.json           TypeScript tests configuration

Enter fullscreen mode Exit fullscreen mode

angular.json

 "outputPath": "dist/angular-poc",
 // Replace below Command in place of above one
 "outputPath": "dist/angular-poc/browser",
Enter fullscreen mode Exit fullscreen mode

Include below configuration into angular.json file

{
  "server": {
    "builder": "@angular-devkit/build-angular:server",
    "options": {
      "outputPath": "dist/angular-poc/server",
      "main": "server.ts",
      "tsConfig": "tsconfig.server.json",
      "inlineStyleLanguage": "scss"
    },
    "configurations": {
      "production": {
        "outputHashing": "media",
        "fileReplacements": [
          {
            "replace": "src/environments/environment.ts",
            "with": "src/environments/environment.prod.ts"
          }
        ]
      },
      "development": {
        "optimization": false,
        "sourceMap": true,
        "extractLicenses": false
      }
    },
    "defaultConfiguration": "production"
  },
  "serve-ssr": {
    "builder": "@nguniversal/builders:ssr-dev-server",
    "configurations": {
      "development": {
        "browserTarget": "angular-poc:build:development",
        "serverTarget": "angular-poc:server:development"
      },
      "production": {
        "browserTarget": "angular-poc:build:production",
        "serverTarget": "angular-poc:server:production"
      }
    },
    "defaultConfiguration": "development"
  },
  "prerender": {
    "builder": "@nguniversal/builders:prerender",
    "options": {
      "routes": ["/"]
    },
    "configurations": {
      "production": {
        "browserTarget": "angular-poc:build:production",
        "serverTarget": "angular-poc:server:production"
      },
      "development": {
        "browserTarget": "angular-poc:build:development",
        "serverTarget": "angular-poc:server:development"
      }
    },
    "defaultConfiguration": "production"
  }
}
Enter fullscreen mode Exit fullscreen mode

src/app/app.module.ts

Add below code instead of BrowserModule

BrowserModule.withServerTransition({ appId: "serverApp" });
Enter fullscreen mode Exit fullscreen mode

src/app/app.server.module.ts

Create a new file inside src/app folder

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],
})
export class AppServerModule {}
Enter fullscreen mode Exit fullscreen mode

src/main.server.ts

Create a new file inside the project directory

/***************************************************************************************************
 * Initialize the server environment - for example, adding DOM built-in types to the global scope.
 *
 * NOTE:
 * This import must come before any imports (direct or transitive) that rely on DOM built-ins being
 * available, such as `@angular/elements`.
 */
import "@angular/platform-server/init";

import { enableProdMode } from "@angular/core";

import { environment } from "./environments/environment";

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

export { AppServerModule } from "./app/app.server.module";
export { renderModule, renderModuleFactory } from "@angular/platform-server";
Enter fullscreen mode Exit fullscreen mode

src/main.ts

Add below code instead of platformBrowserDynamic().bootstrapModule(AppModule)

document.addEventListener("DOMContentLoaded", () => {
  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch((err) => console.error(err));
});
Enter fullscreen mode Exit fullscreen mode

tsconfig.server.json

Create a new file inside main directory

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "extends": "./tsconfig.app.json",
  "compilerOptions": {
    "outDir": "./out-tsc/server",
    "target": "es2019",
    "types": ["node"]
  },
  "files": ["src/main.server.ts", "server.ts"],
  "angularCompilerOptions": {
    "entryModule": "./src/app/app.server.module#AppServerModule"
  }
}
Enter fullscreen mode Exit fullscreen mode

server.js

Create a new file inside project directory

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

import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';

// 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-poc/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/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'
// The below code is to ensure that the server is run 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 './src/main.server';
Enter fullscreen mode Exit fullscreen mode

package.json

Use dev:ssr to run app locally

Use build:ssr to generate the production build

"scripts": {
 "dev:ssr": "ng run angular-poc:serve-ssr",
 "serve:ssr": "node dist/angular-poc/server/main.js",
 "build:ssr": "ng build && ng run angular-poc:server",
 "prerender": "ng run angular-poc:prerender"
}
Enter fullscreen mode Exit fullscreen mode
 "dependencies": {
  "@angular/platform-server": "~12.2.0",
  "@nguniversal/express-engine": "^12.1.0",
  "express": "^4.15.2"
}
Enter fullscreen mode Exit fullscreen mode
 "devDependencies": {
  "@nguniversal/builders": "^12.1.0",
  "@types/express": "^4.17.0"
}
Enter fullscreen mode Exit fullscreen mode

Usage

npm run dev:ssr
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, you learned how to setup Server side rendering with Angular Application.

You can modify the configuration as per your needs.

Github source-code:- https://github.com/balajipatnam/angular/tree/main/ssr

Suggestions are welcome to improve this package.

Top comments (0)

Here is a post you might want to check out:

Regex for lazy developers

regex for lazy devs

Sorry for the callout πŸ˜†