Angular Development Practices: Six Key Principles for Service-Side Rendering
In this article, we will delve into the world of Angular development and explore six key principles for service-side rendering. We will examine the Angular Universal platform, which provides a technology for running Angular applications on the server, and discuss the benefits of server-side rendering, including improved performance and better search engine optimization (SEO).
Angular Universal: A Unified Platform for Service-Side Rendering
Angular Universal is a unified platform that provides a technology for running Angular applications on the server. This platform allows developers to create server-side rendered applications that can be executed on the server and then sent to the client as a pre-rendered HTML page. This approach provides several benefits, including improved performance and better SEO.
Why Should We Render Server-Side?
There are three main reasons why we should render server-side:
- Help Web Crawlers (SEO): Google, Bing, Baidu, Facebook, Twitter, and other social media sites or search engines rely on web crawlers to index the content of our application. These web crawlers may not be able to navigate our Angular application, which is highly interactive, and index it. Angular Universal allows us to generate a static version of our application that is easy to search and link to, without the need for JavaScript.
- Improve Performance on Mobile Phones and Low-Power Devices: Some devices do not support JavaScript or perform poorly when running JavaScript. In these cases, server-side rendering provides a non-JavaScript version of our application that can be used as a fallback. This approach is particularly useful for devices that cannot run our application smoothly.
- Fast Display Home: A fast display home is essential for attracting users. If our page takes more than three seconds to load, 53% of mobile users will abandon it. By generating a “landing page” for our application using Angular Universal, we can provide a fast and smooth user experience, even when our application is still loading.
Examples Resolve
To demonstrate the principles of Angular Universal, we will create a sample project called angular-universal-starter. This project will be based on the Angular CLI development build and will include the necessary configuration for server-side rendering.
Installation Tools
Before we begin, we need to install the following packages:
@angular/platform-server: The server element that provides a DOM implementation for the server.@nguniversal/module-map-ngfactory-loader: A module that provides processing for server-side rendering under loaded conditions.@nguniversal/express-engine: An Express engine that provides a server for Angular Universal applications.ts-loader: A server application for translation.express: A Node Express server.
We can install these packages using the following command:
npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine express
Project Configuration
To configure our project for server-side rendering, we need to create the following files:
- Server-side application modules:
src/app/app.server.module.ts - Client application modules:
src/app/app.module.ts - Server boot file:
src/main.server.ts - Client boot file:
src/main.ts - TypeScript configuration for server:
src/tsconfig.server.json - Angular CLI configuration file:
.angular-cli.json - Node Express service program:
server.ts
Server-Side Application Modules
The server-side application module is responsible for rendering the application on the server. We can create this module by importing the necessary modules and declaring the application components.
import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { AppBrowserModule } from './app.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
AppBrowserModule,
ServerModule,
ModuleMapLoaderModule,
ServerTransferStateModule
],
bootstrap: [AppComponent]
})
export class AppServerModule {}
Client Application Modules
The client application module is responsible for rendering the application on the client. We can create this module by importing the necessary modules and declaring the application components.
import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { APP_ID, Inject, NgModule, PLATFORM_ID } from '@angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { TransferHttpCacheModule } from '@nguniversal/common';
import { isPlatformBrowser } from '@angular/common';
import { AppRoutingModule } from './app.routes';
@NgModule({
imports: [
AppRoutingModule,
BrowserModule.withServerTransition({ appId: 'my-app' }),
TransferHttpCacheModule,
BrowserTransferStateModule,
HttpClientModule
],
declarations: [AppComponent, HomeComponent],
providers: [],
bootstrap: [AppComponent]
})
export class AppBrowserModule {
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
@Inject(APP_ID) private appId: string
) {
const platform = isPlatformBrowser(platformId) ? 'in the browser' : 'on the server';
console.log(`Running ${platform} with appId = ${appId}`);
}
}
Server Boot File
The server boot file is responsible for rendering the application on the server. We can create this file by importing the necessary modules and declaring the application components.
export { AppServerModule } from './app/app.server.module';
Client Boot File
The client boot file is responsible for rendering the application on the client. We can create this file by importing the necessary modules and declaring the application components.
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppBrowserModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
document.addEventListener('DOMContentLoaded', () => {
platformBrowserDynamic().bootstrapModule(AppBrowserModule);
});
TypeScript Configuration for Server
The TypeScript configuration for the server is responsible for compiling the server-side code. We can create this configuration by importing the necessary modules and declaring the application components.
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "commonjs",
"types": ["node"]
},
"exclude": ["test.ts", "**/ *.spec.ts"],
"angularCompilerOptions": {
"entryModule": "app/app.server.module#AppServerModule"
}
}
Angular CLI Configuration File
The Angular CLI configuration file is responsible for configuring the Angular CLI. We can create this file by importing the necessary modules and declaring the application components.
{
"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": "",
"styles": ["styles.scss"],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
Node Express Service Program
The Node Express service program is responsible for rendering the application on the server. We can create this program by importing the necessary modules and declaring the application components.
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';
enableProdMode();
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');
import { ngExpressEngine } from '@nguniversal/express-engine';
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'));
// * - Example Express Rest API endpoints
// app.get('/api/**', (req, res) => {});
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser'), { maxAge: '1y' }));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
const server = app.listen(PORT, () => {
console.log(` App is running on http://localhost:${PORT}`);
});
By following these steps, we can create a server-side rendered application using Angular Universal. This approach provides several benefits, including improved performance and better SEO.