Server-Side Rendering (SSR) Concepts
What is Server-Side Rendering (SSR)?
Server-Side Rendering (SSR) is a web rendering technique that executes JavaScript code on the server to generate a complete HTML page, which is then sent to the browser. Unlike traditional client-side rendering, where a skeletal HTML page with JavaScript is sent and rendered dynamically in the browser, SSR delivers fully rendered content. This approach enhances First Contentful Paint (FCP), improves Search Engine Optimization (SEO), and provides a better user experience.
Advantages of Angular SSR
- Faster First Contentful Paint: The browser receives a complete HTML page, eliminating the need to wait for JavaScript to download and execute.
- SEO-Friendly: Search engine crawlers can directly access page content, improving indexability.
- Enhanced User Experience: Users in low-bandwidth environments see basic content instead of a blank page.
Enabling Angular SSR
Install Necessary Dependencies
Install Angular Universal and related dependencies:
ng add @nguniversal/express-engineThis installs the Express engine, configures the project, and generates files needed for SSR.
Build the SSR Application
Build the Angular application with:
npm run build:ssrThis generates two directories: dist/client (client-side code) and dist/server (server-side code).
Start the SSR Server
Launch the SSR development server:
npm run serve:ssrThis starts an Express server to handle SSR requests.
Practicing Angular SSR
Let’s explore Angular SSR implementation through a simple example.
Step 1: Create an Angular Project
ng new angular-ssr-example
cd angular-ssr-exampleStep 2: Install Angular Universal
ng add @nguniversal/express-engineThis updates project configurations and installs required dependencies.
Step 3: Modify AppModule
Ensure src/app/app.module.ts is ready for SSR:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF } from '@angular/common';
import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
import { ServerModule } from '@angular/platform-server';
@NgModule({
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
RouterModule.forRoot([]),
ServerModule, // Add ServerModule
],
declarations: [AppComponent],
providers: [{ provide: APP_BASE_HREF, useValue: '/' }],
bootstrap: [AppComponent],
})
export class AppModule {}Create a server-side module, typically named app.server.module.ts:
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],
entryComponents: [AppComponent],
})
export class AppServerModule {}Step 4: Build the SSR Application
Build the application:
npm run build:ssrStep 5: Start the SSR Server
Launch the SSR server:
npm run serve:ssrVisit http://localhost:4000 in your browser to view the SSR application.
Considerations
- Code Splitting: Ensure routing supports code splitting to load modules on demand, reducing initial load time.
- State Management: Manage state consistency between server and client during SSR.
- Asynchronous Data Loading: Load data before component initialization in SSR using the
ngOnInitlifecycle hook and asynchronous strategies. - SEO Optimization: While SSR aids SEO, ensure metadata (e.g., titles, descriptions) is correctly generated server-side.
Conclusion
Angular SSR provides powerful tools and APIs for server-side rendering, crucial for enhancing user experience, SEO, and performance. By following these steps, you can implement SSR in your projects and leverage its benefits. However, SSR introduces complexities like state management and asynchronous data loading, requiring careful design and implementation.
Using Angular Universal for SSR
Angular Universal is Angular’s official SSR solution, enabling server-side prerendering of Angular applications to deliver static HTML to clients, significantly improving FCP and SEO.
Initializing the Project
Create a new Angular project if you don’t have one:
ng new my-ssr-app
cd my-ssr-appInstalling Angular Universal
Add Angular Universal to the project using Angular CLI:
ng add @nguniversal/express-engineThis installs necessary packages, including @nguniversal/express-engine, @nguniversal/module-map-ngfactory-loader, and the browser version of zone.js.
Modifying Project Configuration
Angular Universal requires specific configurations. In angular.json, update the architect object’s build and serve configurations:
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
},
{
"replace": "src/index.html",
"with": "src/index-ssr.html"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
// ...
]
}
}This specifies index-ssr.html as the template for production mode, required for Angular Universal.
Creating Server-Side Code
Define the server-side module in src/app/app.server.module.ts:
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],
entryComponents: [AppComponent]
})
export class AppServerModule {}Configuring the Server
In src/main.server.ts, set up the Express server to use Angular Universal:
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './app/app.server.module';
import { existsSync } from 'fs';
// The Express app is exported for use by serverless functions
export function app() {
const server = express();
const distFolder = join(process.cwd(), 'dist/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index.html';
// Universal 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 });
});
return server;
}
function run() {
const port = process.env.PORT || 4000;
// Start the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Ensure the server runs 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 './app/main';This code sets up an Express server to render HTML pages using Angular Universal’s engine.
Building the SSR Version
Build the SSR version of the application:
ng build --prod
ng run my-ssr-app:server:productionThis generates dist/server and dist/browser folders containing server-side and client-side code, respectively.
Running the SSR Server
Start the SSR server:
node dist/server/main.jsYour Angular application should now run with server-side rendering. Visit http://localhost:4000 to see the result.
Testing and Debugging
During development, frequently build and test the SSR version. Angular CLI provides a serve-ssr command to automatically build and start the SSR server:
ng run my-ssr-app:serve-ssrThis launches a development server that rebuilds on code changes.
Considerations
- State Management: Ensure state synchronization between server and client. Angular’s
NgZonecan assist with this. - Asynchronous Data Loading: Load data before component initialization in SSR using the
ngOnInitlifecycle hook and asynchronous strategies. - Error Handling: Implement robust error handling in SSR to prevent rendering failures due to errors.



