Server-Side Rendering in Angular with Angular Universal

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 pages as HTML files that you serve later.

Angular universal

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:

export { AppServerModule } from './app/app.server.module';

Create a server.ts file in the project’s root directory and add 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 (Note that this file will be hidden in your ide, so in Terminal+ execute the command open .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

The console window should say Node server listening on http://localhost:4000

Open a browser to http://localhost:4000/.

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.

PS: this article is inspired by Fireblog Tutorial.


Angular IDE

Most of Angular tutorials on dunebook.com were created using Angular IDE. The Angular IDE by Genuitec is built specifically for Angular 2+. It is simple for beginners; powerful for experts. It

This IDE supports advanced editing of TypeScript 2.0  which improves your development effectively. Some cool features of Angular IDE are Listed Below.

  • Real-time validation and display of errors as you type code.
  • Auto-completion of code across your project.
  • Syntax-aware source colouring and occurrence highlighting.
  • Block and full-file formatting with advanced settings.
  • Integrated support for TypeScript debugging
  • Built first for tsconfig.json management.

 

    Download IDE   

Comments

Pin It on Pinterest

Shares