Server-Side Rendering in Angular

Written by rathore | Published 2020/04/03
Tech Story Tags: angular | server-side-rendering | angularjs | frontend-development | web-development | coding | tutorial | programming

TLDR The technology that allows us to run our Angular applications on the server is called server-side rendering (SSR) It can generate and serve those pages in response to requests from browsers. In order to improve the user experience, we want to render the pages server side and send the generated static content to the client side. This will ensure that our content is crawlable by search engines and also allow users with feature phones to consume our content. In this article you will learn how to add Angular Universal support to any existing Angular app.via the TL;DR App

The technology that allows us to run our Angular applications on the server is described in the Angular docs as Angular Universal.
Angular Universal generates static application pages on the server through a process called server-side rendering (SSR).

It can generate and serve those pages in response to requests from browsers. It can also pre-generate Angular template as HTML files that you serve later.
A normal  Angular application executes in the browser, rendering pages in the DOM in response to user actions.
In order to improve the user experience, we want to render the pages server side and send the generated static content to the client side, this will ensure that our content is crawlable by search engines and also allow users with  feature phones to consume our content.

In this article you will learn how to add Angular Universal support to any existing Angular app. Let’s get started by installing dependencies

Server Side Rendering  – Install dependencies
In order to implement server side rendering we need to install some additional dependencies.
Open Terminal enter the following commands:
$ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine
The root AppModule
Open file 
src/app/app.module.ts
 and find the
BrowserModule
import in the
NgModule
metadata.

Replace that import with this one:
BrowserModule.withServerTransition({ appId: 'your App-ID' }),
Create an 
app.server.module.ts
 file in the 
src/app/
 directory with the following AppServerModule code:
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
 
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
 
@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule
  ],
  providers: [
    // Add universal-only providers here
  ],
  bootstrap: [ AppComponent ],
})
export class AppServerModule {}
Create a 
main.server.ts 
file in the src directory with the following code:
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

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

import * as express from 'express';
import { join } from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
  res.status(404).send('data requests are not supported');
});

// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});

// Start up the Node server
app.listen(PORT, () => {
  console.log(`Node server listening on http://localhost:${PORT}`);
});
Tip: This sample server is not secure!
Create a 
tsconfig.server.json
 file in the 
/src
 directory to configure TypeScript and AOT compilation of the universal app.
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "commonjs",
    "types": []
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ],
  "angularCompilerOptions": {
    "entryModule": "app/app.server.module#AppServerModule"
  }
}
Create a 
webpack.server.config.js
 file in the project root directory with the following code.
const path = require('path');
const webpack = require('webpack');
 
module.exports = {
  entry: { server: './server.ts' },
  resolve: { extensions: ['.js', '.ts'] },
  target: 'node',
  // this makes sure we include node_modules and other 3rd party libraries
  externals: [/(node_modules|main\..*\.js)/],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [{ test: /\.ts$/, loader: 'ts-loader' }]
  },
  plugins: [
    // Temporary Fix for issue: https://github.com/angular/angular/issues/11580
    // for 'WARNING Critical dependency: the request of a dependency is an expression'
    new webpack.ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/,
      path.join(__dirname, 'src'), // location of your src
      {} // a map of your routes
    ),
    new webpack.ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/,
      path.join(__dirname, 'src'),
      {}
    )
  ]
};
Update apps section of
.angular-cli.json 
"apps": [{
 "root": "src",
 "outDir": "dist/browser",
 "assets": ["assets", "favicon.ico"],
 "index": "index.html",
 "main": "main.ts",
 "polyfills": "polyfills.ts",
 "test": "test.ts",
 "tsconfig": "tsconfig.app.json",
 "testTsconfig": "tsconfig.spec.json",
 "prefix": "app",
 "styles": ["styles.css"],
 "scripts": [],
 "environmentSource": "environments/environment.ts",
 "environments": {
 "dev": "environments/environment.ts",
 "prod": "environments/environment.prod.ts"
 }
 },
 {
 "platform": "server",
 "root": "src",
 "outDir": "dist/server",
 "assets": ["assets", "favicon.ico"],
 "index": "index.html",
 "main": "main.server.ts",
 "test": "test.ts",
 "tsconfig": "tsconfig.server.json",
 "testTsconfig": "tsconfig.spec.json",
 "prefix": "app",
 "styles": ["styles.css"],
 "scripts": [],
 "environmentSource": "environments/environment.ts",
 "environments": {
 "dev": "environments/environment.ts",
 "prod": "environments/environment.prod.ts"
 }
 }
],

Build and run with universal

Now that you’ve created the TypeScript and Webpack config files, you can build and run the Angular Universal application.

First add the 
build
 and serve commands to the scripts section of the 
package.json
:
"scripts": {
    ...
    "build:universal": "npm run build:client-and-server-bundles && npm run webpack:server",
    "serve:universal": "node dist/server.js",
    "build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
    "webpack:server": "webpack --config webpack.server.config.js --progress --colors",
    ...
}

Build Universal

From Terminal, type:
npm run build:universal

Serve Universal

After building the application, start the server.
npm run serve:universal
Finally. Open a browser window to 
http://localhost:4000/
 and access your Server-side rendered Angular app.
Conclusion
With server-side rendering, we can ensure that search engines, browsers with Javascript disabled, or browsers without Javascript can still access our site content.
References
  • https://codesource.io/build-a-server-monitoring-app-using-node-angular/
Previously published at https://codesource.io/server-side-rendering-in-angular/

Written by rathore | Founder @ dunebook.com and CodeSource.io
Published by HackerNoon on 2020/04/03