Installing Additional Dependencies

In order to implement SSR, we’ll need to add a few more tools to the project that aren’t included by default. Open up a terminal in your project folder and run the following command:

npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine

Modify existing files

You will need to modify existing files in the project as follows:

./src/app/app.module.ts

Instead of having the AppModule simply import the BrowserModule, you will use the BrowserModule.withServerTransition method instead.

BrowserModule.withServerTransition({ appId: 'ssr-app' })

Angular adds the provided appId value to the style-names of HTML elements that are rendered by the server so that the client can find and replace those elements with client rendered elements. The supplied appId can be any string.

./angular.json

Next up, we’re going to change the configuration of the Angular project itself. We are going to modify the project to run two separate builds: the code that runs in the browser and the code that runs on the server.

We start by changing the output directory of the default build target to dist/browser

Changing the output directory of the browser build target

Next, we create a new build target called “server”. The configuration for this target refers to files that will be added later.


"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json"
}
},

./package.json

Finally, we’ll need to add a few new npm scripts that we’ll use to build and run the the server-side rendered application.

"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
"serve:ssr": "node dist/server",
"build:client-and-server-bundles": "ng build --prod && ng run ssr-app:server",
"webpack:server": "webpack --config webpack.server.config.js --progress --colors",

Take note that the “build:client-and-server-bundles” script uses the name of the app to run the server build target and is of the form ng build --prod && ng run <app-name>:server .

NPM scripts for building and running the server-side rendered application

Adding new files

Now that the modifications have been made, let’s add some new files to the project.

./src/app/app.server.module.ts

First, let’s add the AppServerModule class. The Angular documentation describes the class as follows:

The app server module class (conventionally named AppServerModule) is an Angular module that wraps the application’s root module (AppModule) so that Universal can mediate between your application and the server. AppServerModule also tells Angular how to bootstrap your application when running as a Universal app.

./src/app/app.server.module.ts

./src/main.server.ts

Next, we’ll add an entry point for building the server side code. This is referenced by the Angular CLI server target configuration we added previously. This file only needs one line for exporting the AppServerModule.

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

./src/tsconfig.server.json

We then need to add a configuration file for telling TypeScript how to build the server-side Angular code.

./src/tsconfig.server.json

./webpack.server.config.js

We also need to add a Webpack configuration file so that we can transpile the web server itself (the server.ts file we’ll add next).

./webpack.server.config.js

./server.ts

And finally, we need to add a file that defines our web server powered by the Express framework. The web server will run in Node.js and will use the browser and server build outputs to respond to requests for the web app.

./server.ts

Angular with SSR

Once we made the necessary project modifications and additions, we should now be able to build and run our final product.

Open a terminal in the root directory of the project and enter the following command to build the code.

npm run build:ssr

The first time you run this build, you may get prompted to install webpack-cli. If so, then go ahead and proceed with the installation by entering “yes”.

Proceeding with webpack-cli installation

Once the build finishes, run your web server…

npm run serve:ssr

The output should tell you where to access your web app from the browser. If you didn’t change the default port in server.ts or defined an environment variable otherwise, then it should be http://localhost:4000.

If you take a look at the initial response from the server for our new Angular app, you should see the following HTML content. Notice how the app-root element now has a bunch of content inside:

Initial response from server with SSR
Source: https://giphy.com

YES!!! Our Express server used Angular Universal to render the initial view of our app and placed the rendered content inside of the app-root element in the response to the browser. Now search engine bots should be able to crawl the links that are on the page and social sharing previews should display the page as the user would see it instead of a blank thumbnail.

Additional Considerations

If you want to do even a little bit more than the app created by ng new, then there are additional things you need to consider as you extend your app or implement SSR for an existing Angular project.

  1. The server does not have access to browser APIs

If your components need access to browser APIs such as window or local storage, you are going to have to make your code aware of the platform its running on (server vs. browser) to determine whether those APIs available. This could be made easier by making sure that you wrap those APIs in services that are smart enough to fall back to something else.

2. You need to use absolute URLs when making HTTP requests from code running on the server.

If you have a requirement for making HTTP requests from your app, then your code will need make sure that it uses absolute URLs when running on the server. If you don’t need relative URLs, then always going absolute will keep you safe.

3. If your landing page has dynamic content, you need to use TransferState to avoid a flash on the page.

While the server-side rendered content is displayed, the browser downloads the client-side code in the background to bootstrap the client-side Angular application. Once the bootstrapping is complete, Angular Universal will replace the server-side view with the normal client-side application. If the client-side components are still waiting for data from HTTP requests when this hot swapping occurs, then the user will briefly see content-less client-side components until the data returns.

To prevent this from happening, you can use the Transfer State service to cache the results of HTTP calls made on the server by your components, have Angular Universal send that data to the browser along with everything else, and have your client-side app use that data instead of making duplicate HTTP calls.

In Conclusion

I’m personally hoping to use this setup as a launching pad for my next project. The time when you had to avoid single page app frameworks for SEO use cases seems to have come to an end with the arrival of server-side rendering support. You can make bots happy and users even happier with faster page loads.




SOURCE