Prerender Angular Application using Angular Universal Prerenderer
This article is a second part of the Angular Universal Series.
This series is focusing on improving the initial loading time of angular application as well as adding support for the web crawlers. This helps us to improve SEO and performance on low powered devices.
In the previous article, we have seen
Before we start to implement prerendering in angular application lets first see what is prerendering and how it is different from server-side rendering.
In short definition:
In both rendering technique, we generate static HTML pages which can be easily crawled by crawlers. Just the major difference is the location where HTML pages are generated.
In Server-Side Rendering Technique, when the user navigates to the URL => for example /books
route, the server compiles the application(render on a server) and sends the generated HTML page back to the client.
While In Prerendering Technique, All routes of the application are compiled at build time and saved as static HTML pages on file-system. These HTML pages can be served to the client using CDN. So whenever the user navigates to any route, he/she will get prerendered HTML pages.
This is the reason why the Prerendering technique is faster then Server-Side Rendering.
Prerendering has the advantage of being able to serve requests stupidly fast, because nothing needs to be generated on the fly.
In fact, since your site’s responses are all generated ahead of time, you can store the files all over the world on a CDN (on the edge). This gives your site ridiculously fast response times.
Server-Side Rendering adds extra overhead for processing (compilation on the server).
With this advantage, Prerendering technique also has some drawbacks:
In scenarios, where we need to generate pages dynamically or where content changes required frequently, we should go with Server Side Rendering.
Angular Universal gives us feature to implement both rendering technique.
For demonstrating angular application prerendering, I have created one sample angular application called Game Of Thrones Info. This application has the following routes:
We will use Angular Universal to perform server-side rendering and prerendering.
Implement SSR and Prerendering along with this article. get the initial app setup at //github.com/ngdevelop-tech/got-prerender-demo.git (checkout to the initial-setup
branch)
To capture the initial loading time of our application we will use one of the important performance matric FCP (First Contentful Paint).
FCP marks the time at which the first text or image is painted.
We will add the following script in index.html
to measure FCP.
<script> // Log the value of FCP to the console | //web.dev/fcp/#measure-fcp-in-javascript const observer = new PerformanceObserver((list) => { for (const entry of list.getEntriesByName('first-contentful-paint')) { console.log('FCP: ', entry.startTime); observer.disconnect(); } }) observer.observe({type: 'paint', buffered: true}); </script>
We first measure the performance of our application without SSR and Prerendering. we can use this states for comparison after implementing SSR and Prerendering.
Our application performance looks like below:
As you can see above, it takes nearly ~890 ms for first contentful paint, which is due to it consumes some time in scripting and compiling JS files.
Note: Performance time will vary with application to application, as you refresh the page and when cache is disabled/enabled.
Now let’s add server-side rendering and prerendering using Angular Universal.
We will use angular universal to implement Server-Side Rendering and Prerendering.
👉 Refer to this article for more details about Server-Side Rendering implementation in Angular application.
Execute the following angular universal schematic to implement server-side rendering.
ng add @nguniversal/express-engine
This will add and update the required files for Server Side Rendering and Prerendering.
👉 Angular Universal v9 added CLI Builders –
Angular Universal ng add
schematic will update package.json
and angular.json
as follows
package.json
andangular.json
{ ... "newProjectRoot": "projects", "projects": { ... "got-prerender-demo": { "architect": { ... "serve-ssr": { "builder": "@nguniversal/builders:ssr-dev-server", "options": { "browserTarget": "got-prerender-demo:build", "serverTarget": "got-prerender-demo:server" }, "configurations": { "production": { "browserTarget": "got-prerender-demo:build:production", "serverTarget": "got-prerender-demo:server:production" } } }, "prerender": { "builder": "@nguniversal/builders:prerender", "options": { "browserTarget": "got-prerender-demo:build:production", "serverTarget": "got-prerender-demo:server:production", "routes": [ "/" ] }, "configurations": { "production": {} } } } }}, "defaultProject": "got-prerender-demo" }
{ "name": "got-prerender-demo", "scripts": { ... "dev:ssr": "ng run got-prerender-demo:serve-ssr", "serve:ssr": "node dist/got-prerender-demo/server/main.js", "build:ssr": "ng build --prod && ng run got-prerender-demo:server:production", "prerender": "ng run got-prerender-demo:prerender" } }
Great!!! We are done with default SSR and prerendering implementation ✨✨✨. Now Let’s measure SSR application performance📈.
Build SSR application and serve it using the following command
npm run build:ssr && npm run serve:ssr
As you can see above, it now nearly takes ~262 ms for first contentful paint. This is a great improvement in application loading time. Also, this application can be crawled using any crawler.
To prerender static routes we just need to execute the following command. This will look for all static routes using guess-parser and prerender it.
npm run prerender
As you can see, it will generate all static routes of Game of thrones application. Prerendering also renders the data received from API response.
When we publish this application, You might get following question 🤔
When the user navigates to any route (like /books
), How Server/CDN knows, from where it needs to give a response? is it from a /books/index.html
or from an angular application route?
The answer is nowadays CDN / Server is smart enough to detect the valid page response, when we navigate to a route like /books
, if it finds folder like /books/index.html
, it will render that prerendered page otherwise it will look for the route in angular app.
Guess-parser is able to detect static routes, but it is unable to detect parameterized routes like /characters/:id
, because how could it knows what are the -real – possible values of id !!!
Fortunately, Angular Universal has also added feature to manually assign the real possible values for route parameter ( like :id
).
We can assign it using two ways :
👉 1st way:
We can manually provide routes using --routes
flag for each route in the terminal.
ng run got-prerender-demo:prerender --routes "/characters/583" --routes "/characters/584"
or configuring each route in angular.json
routes property of prerender
builder configuration.
"prerender": { "builder": "@nguniversal/builders:prerender", "options": { "browserTarget": "got-prerender-demo:build:production", "serverTarget": "got-prerender-demo:server:production", "routes": [ "/characters/583", "/characters/584" ] }, "configurations": { "production": {} } }
👉 2nd way:
If we have a large number of routes we can specify each route in a separate .txt
file and assign it using --routesFile
flag or prerender builder routesFile
parameter as below.
For example, I have created dynamic-routes.txt
in src
folder, and provide all required additional routes.
/characters/1 /characters/2 /characters/3 /characters/583 /characters/584 /characters/585
We can use this routes file by assigning it with --routesFile
flag as below
ng run prerender --routesFile 'dynamic-routes.txt'
or specify routesFile
in angular.json
prerender
builder configuration.
"prerender": { "builder": "@nguniversal/builders:prerender", "options": { "browserTarget": "got-prerender-demo:build:production", "serverTarget": "got-prerender-demo:server:production", "routesFile": "./dynamic-routes.txt" }, "configurations": { "production": {} } }
👉 We only need to include additional routes that aren’t automatically detected by the guess-parser.
👉 For a large number of parameterized routes we can write a script to generate routes in .txt
file
Once prerendered routes are generated we can test it’s performance by launching dist/[app_name]/browser
folder on http-server or lite-server.
As you can see, We receive first contentful paint now in ~200 ms.
Though in the local machine it is difficult to compare the loading time of each rendering technique, it gives some idea of how it will perform in production.
Great !!! ✨✨✨ We are done with both Server Side Rendering and Prerendering Implementation.
Find the complete implementation of SSR and Prerendering at below repository
//github.com/ngdevelop-tech/got-prerender-demo.git
Checkout to the initial-setup
branch for application without SSR and Prerendering
In this article, we have seen
I hope you like this article, please provide your valuable feedback and suggestions in the comment section below🙂.
For more updates, Follow us 👍 on NgDevelop Facebook page.
Charts help us to visualize large amounts of data in an easy-to-understand and interactive way. …
Generating reports, invoices, blog content, documents, guidelines, forms, etc in PDF form is a common…
Web Page title, description, and other meta tags are very important for the Search Engine…
Google Analytics is a popular analytical tool for web and mobile applications from Google. It…
Chrome DevTools provides amazing features to debug source code, capture element state, update and test…
Angular Charts Series Articles Best Angular Chart Libraries How to use ngx-charts in angular application…