Prerender Angular application — be SEO

Prerender your application with @ng-toolkit/universal, and make it readable for search engine robots.

Apr 7 · 4 min read

In one of my previous post I was writing about how to make your Angular application SEO friendly by using Server-Side Rendering — Angular Universal. This solution involves NodeJS to launch Angular framework on the server side and ship to the end-user fully rendered html, based on user request; but… What if you don’t have compute resources to render view on the server side? What if you just don’t want to use it? Today I am going to show another use case for Angular Universal — prerendering application at the build time.

Create basic application and check if it is SEO-friendly

Let’s create an angular app and check, how robots see it:

ng new myApp --style css --routing true
cd myApp
ng serve

Now in the other terminal window execute curl command and take a look at the output:

$ curl localhost:4200<!doctype html>
<html lang="en">
<meta charset="utf-8">
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript" src="styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script></body>

Do you see the problem? What we got inside <body>? Nothing. App bootstrap node; and some JavaScript. How to make this output reacher?

ng add @ng-toolkit/universal

Let’s make a use from one of the @ng-toolkit functionalities — Universal

ng add @ng-toolkit/universal

Above command should launch a schematics, which apart of installing dependencies necessary for Angular Universal, add to your package.json couple useful scripts. Let’s use one of them:

npm run build:prerender

Now, you should see new catalog:dist/static , inside which all your application views should be prerendered:

ls -la dist/static/total 808
drwxr-xr-x 10 mtreder staff 320 Apr 5 16:05 .
drwxr-xr-x 7 mtreder staff 224 Apr 5 16:05 ..
-rw-r--r-- 1 mtreder staff 23664 Apr 5 16:05 3rdpartylicenses.txt
-rw-r--r-- 1 mtreder staff 57777 Apr 5 16:05 es2015-polyfills.c5dd28b362270c767b34.js
-rw-r--r-- 1 mtreder staff 5430 Apr 5 16:05 favicon.ico
-rw-r--r-- 1 mtreder staff 2290 Apr 5 16:05 index.html
-rw-r--r-- 1 mtreder staff 265652 Apr 5 16:05 main.1039dbc40b97d189745e.js
-rw-r--r-- 1 mtreder staff 41994 Apr 5 16:05 polyfills.8bbb231b43165d65d357.js
-rw-r--r-- 1 mtreder staff 1440 Apr 5 16:05 runtime.26209474bfa8dc87a77c.js
-rw-r--r-- 1 mtreder staff 0 Apr 5 16:05 styles.3ff695c00d717f2d2a11.css

All that magic thanks to the prerender.ts script, which fires Angular Universal, runs the server locally, visit it and save rendered HTML to the static files.

Let’s launch server now:

npm run serve:prerender> my-project@0.0.0 serve:prerender /Users/mtreder/myProject
> node static.js
Listening on localhost:8080

And check the curl output:

curl localhost:8080<!DOCTYPE html><html lang="en"><head>
<meta charset="utf-8">
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.3ff695c00d717f2d2a11.css"><style ng-transition="serverApp">
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzcmMvYXBwL2FwcC5jb21wb25lbnQuY3NzIn0= */</style></head>
<app-root _nghost-sc0="" ng-version="7.2.10"><div _ngcontent-sc0="" style="text-align:center"><h1 _ngcontent-sc0=""> Welcome to anotherProject! </h1><img _ngcontent-sc0="" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==" width="300"></div><h2 _ngcontent-sc0="">Here are some links to help you start: </h2><ul _ngcontent-sc0=""><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="" rel="noopener" target="_blank">Tour of Heroes</a></h2></li><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="" rel="noopener" target="_blank">CLI Documentation</a></h2></li><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="" rel="noopener" target="_blank">Angular blog</a></h2></li></ul><router-outlet _ngcontent-sc0=""></router-outlet></app-root>
<script type="text/javascript" src="runtime.26209474bfa8dc87a77c.js"></script><script type="text/javascript" src="es2015-polyfills.c5dd28b362270c767b34.js" nomodule=""></script><script type="text/javascript" src="polyfills.8bbb231b43165d65d357.js"></script><script type="text/javascript" src="main.8a1f4f84259890f69850.js"></script>
<script id="serverApp-state" type="application/json">{}</script></body></html

Much better!

Will it visit all routes in my application?

Yes. prerender.ts scans your application modules, looking for the RouterModule import and takes your routing configuration from it. If for any reason it can’t, you can pass routes which should be prerendered explicitly by adding them to the static.paths.ts file:

export const ROUTES = ['/additional-route'];

Prerender script will print out to the console all routes which it is going to visit:

npm run build:prerender
Got following static routes:
And following found in the application:

Inside the dist/static catalog you can find all specified routes:

ls -dl */
drwxr-xr-x 3 mtreder staff 96 Apr 7 13:35 additional-route/
drwxr-xr-x 3 mtreder staff 96 Apr 2 15:41 home/
drwxr-xr-x 3 mtreder staff 96 Apr 2 15:41 posts/

Thanks for reading!

You can support @ng-toolkit project by starring it on GitHub, sharing with friends and placing a donation on OpenCollective, DonorBox or LiberaPay.

In case of bug, please report it on GitHub.

Maciej Treder,

Follow me on Twitter and GitHub to be notified about my activities.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade