Angular Universal is a powerful tool that allows you to perform server-side rendering (SSR) for Angular applications. SSR enhances the performance of your application by pre-rendering the HTML on the server, which is particularly beneficial for improving SEO and the initial load time of your application.
In this guide, we'll walk through the process of setting up Angular Universal with routing in Angular 16, from scratch up until serving the app, and explain how SSR works under the hood.
Improved Performance: Faster initial load time as the server sends the pre-rendered HTML.
SEO Optimisation: Search engines can easily crawl fully rendered pages.
Better User Experience: Reduced time to be interactive for users, especially on slower devices.
If you don’t already have an Angular application, create one using the Angular CLI:
ng new angular-ssr-app
cd angular-ssr-app
Step 2: Add Angular Universal
Add Angular Universal to your project by running the following command:
ng add @nguniversal/express-engine
This command configures your project for SSR with Angular Universal and Express. It creates a server.ts file and updates your project files accordingly.
Generate some components to use with routing. For example, create home and about components:
ng generate component home --module=app.module
ng generate component about --module=app.module
Update your app-routing.module.ts to define routes for the home and about components:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
const routes: Routes = [
{
path: '',
component: HomeComponent,
},
{
path: 'about',
component: AboutComponent,
},
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
initialNavigation: 'enabledBlocking',
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
Update your app.component.html to include links to the routes and a router outlet:
<nav>
<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet>
app.component.scss:
/* Styles for the navigation bar */
nav {
background-color: #2c3e50;
padding: 10px;
text-align: center;
}
/* Styles for the navigation links */
nav a {
color: #ecf0f1;
text-decoration: none;
font-size: 1.2em;
margin: 0 15px;
padding: 5px 10px;
border-radius: 5px;
transition: background-color 0.3s ease;
}
/* Hover effect for navigation links */
nav a:hover {
background-color: #34495e;
}
Home Component
home.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {}
home.component.html:
<div class="home-container">
<h1 class="home-title">Welcome to the Home Page!</h1>
<p class="home-content">
This is the home page of the Angular Universal application.
</p>
</div>
home.component.scss:
/* Add any specific styles for the home component here */
.home-container {
text-align: center;
padding: 20px;
}
.home-title {
font-size: 2.5em;
color: #2c3e50;
margin-bottom: 20px;
}
.home-content {
font-size: 1.2em;
color: #34495e;
}
about.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss'],
})
export class AboutComponent {}
about.component.html:
<div class="about-container">
<h1 class="about-title">About Us</h1>
<p class="about-content">
This is the about page of the Angular Universal application.
</p>
</div>
about.component.scss:
/* Add any specific styles for the about component here */
.about-container {
text-align: center;
padding: 20px;
}
.about-title {
font-size: 2.5em;
color: #27ae60;
margin-bottom: 20px;
}
.about-content {
font-size: 1.2em;
color: #2ecc71;
}
Ensure that your server.ts file correctly handles requests for all routes. The initial setup should cover this, but let's go through it for clarity.
import 'zone.js/node';
import { APP_BASE_HREF } from '@angular/common';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { AppServerModule } from './src/main.server';
// 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-ssr-app/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html'))
? 'index.original.html'
: 'index';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/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';
First, build the client-side app, and the server-side render of parts of the client application with:
npm run build:ssr
This command will create two folders: dist/angular-ssr-app/browser and dist/angular-ssr-app/server.
To serve the application using Angular Universal:
npm run serve:ssr
You should see the output indicating a few new scripts that the server is running:
Node Express server listening on http://localhost:4000
Open your browser and navigate to http://localhost:4000 to see your server-rendered Angular application with routing.
Home Page (/):
User navigates to http://localhost:4000/.
The server receives the request and renders the HomeComponent.
The server sends back fully rendered HTML for the HomeComponent.
A client receives and displays the HTML immediately.
Angular initializes on the client side and takes over for interactive elements.
About Page (/about):
User navigates to http://localhost:4000/about.
Server receives the request and renders the AboutComponent.
Server sends back fully rendered HTML for the AboutComponent.
Client receives and displays the HTML immediately.
Angular initialises on the client side and takes over for interactive elements.
Server-side rendering in Angular Universal works by pre-rendering the Angular application on the server before sending it to the client. Here’s a step-by-step breakdown of the whole angular server-side rendering to client-side rendering process:
Initial Request: When a user makes an initial request to the server, the server uses Angular Universal to bootstrap the Angular application.
Pre-rendering: The Angular application runs on the server, rendering the components and generating the HTML content.
Sending HTML: The server sends the fully rendered HTML to the client's browser.
Client-Side Bootstrapping: Once the HTML is loaded, Angular bootstraps on the client side, taking over from the static HTML and making the application interactive.
This process ensures that the user sees a fully rendered page almost immediately, improving both search engine performance and SEO.
Let's consider a real-world example to showcase the benefits of SSR. Imagine you have an e-commerce website built with Angular.
Scenario Without SSR
When a user visits your homepage, their browser downloads the JavaScript bundles.
The Angular application runs in the browser and fetches data from the server.
The page renders after all these steps, which could take a few seconds on a slow connection.
Scenario With SSR
When a user visits your homepage, the server pre-renders the Angular application.
The user immediately sees the fully rendered HTML content.
Angular bootstraps in the background, making the page interactive without the user noticing any delay.
Faster Load Time: Users see the content immediately, reducing bounce rates.
Better SEO: Search engines can easily crawl and index the pre-rendered HTML.
Improved Performance: Especially on mobile devices and for users with slower internet connections.
Improved User Experience: Users get a faster time to first paint and interactivity, leading to higher engagement and satisfaction.
Implement a fine-grained Universal Application Shell to render content on the server selectively
This improves performance by only rendering what is necessary
Angular Universal has also been integrated with ASP.NET Core (where it invokes a small Node process to manage the server-side rendering, piping all of the data back to ASP.NET and rendering it)
Visit Angular Tutorials to learn fundamental and advanced Angular knowledge from our Angular experts
Check out the Trilon Blog for more articles on Angular, NestJS, and much more!
This tutorial covered how to set up Angular Universal for server-side rendering (SSR) with routing in your Angular application. SSR offers significant benefits such as improved performance, SEO, and user experience by pre-rendering your application on the server.
We walked through the steps of adding Angular Universal to your application code in the app root of the Angular app in your project, setting up routing, creating components, and configuring the server. Additionally, we discussed how SSR works under the hood and provided a real-world example to illustrate its advantages.
By implementing Angular Universal with SSR, you've taken a crucial step towards optimizing your Angular application for better performance and SEO. Feel free to explore customization options, optimize your application for web development and production, and learn about how Angular Universal works to leverage its full potential.
If you have any questions or feedback, please leave a comment below. Happy coding with Angular Universal!
This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.