Uncategorized

Pre-Rendering Strategies

This series of blog posts which I have converted from Youtube videos. I personally have really enjoyed these videos so in order to textually document them, these are essentially some screenshots and rough notes organised. 

This particular post is based on the awesome Jeff Cross Angular Connect 2017 talk. It can be found on the link https://www.youtube.com/watch?v=lkm9xVJFAQM

Pre-rendering is a first class part of Angular, which offers benefits of Angular/Server. It is kept up to date and is well-reviewed by Google as well.

Down below is the difference between a standard code and the one which is pre-rendered.

It is different from AoT compilation. The AoT compilation produces javascript files in order to produce smaller files and faster web experience, while pre-rendering generates an HTML document which is ready to show content.

Pre-rendering exists to satisfy the three priorities mentioned below;

  1. Fast loading
  2. Scrape-able
  3. Crawl-able

Fast loading:

Angular has some things built in, which get optimized for quick download and bootstrap, AoT, build optimizer- which makes tree shaking more powerful and lazy loading- which gives control to the developers when certain parts of the application get loaded.

It gives the opportunity for the improvement which is required to be seized with pre-rendering, i.e. there is a dead time between HTML load and application bootstrap.

For optimizing, there are two key metrics that require focus.

  1. Time to first meaningful paint- when the developer can see a content which is meaningful to them
  2. Time to interactive- when the users’ actions are being responded to, by the application.

Up above, is the graphical representation of when the application is pre-rendered and when it is not.

The blue period is the time which is taken up by the loading, while in the green is the time for bootstrap. On the bootstrap time, once the HTML document is loaded and if the app is not pre-rendered, only the loading is seen until the time Angular is done bootstrapping.

But, with pre-rendering, the time can be shifted on the same graph, by sniffing the amount of time because, the content would be ready to look at, while the Angular would warm up in the browser, will evaluate and finish the bootstrapping and becomes completely interactive.

Scrape-able

Use of social scrapers like Facebook, Twitter, etc. These scrapers prefer specific Meta tags and the prominent ones of those will fall back to <body>. They see the content of the page and they seek canonical URLs (which are the single correct URLs, so they do not duplicate the content). Typically, these scrapers do not run the app and execute the JavaScript.

Up above, is the example of a blog article, which has the description, the title and the image for the article, which shows up correctly on the twitter feed.

These social networks have their own defined Meta tags. Facebook has the open graph protocol and Twitter has a card protocol. These do not require a lot of pre-rendering.

Crawl-able

For the apps to be crawl-able, they require some details like, title, Meta description, canonical URLs and the most important one is the page content. They will execute the JavaScripts, unlike social scrapers, like Google bot. But, they also have some limitations, like they run on an older version of chrome and they still recommend pre-rendering.

The process of pre-rendering

Basically, it has 4 steps:

  1. Render
  2. Serve
  3. Bootstrap the app
  4. Replay events

1.      Render:

Angular server platform provides this renderModuleFactory function that is passed a module that is compiled by the developer. It outputs full string of HTML on the other side, which can be used accordingly.

There are certain misconceptions about pre-rendering like:

⚉ Firstly, that it runs on a server at request time

⚉ Another misconception is that it must render each page exactly as it will appear after the bootstrap

⚉ The same pre-rendering strategy is needed to be applied on the entire app

2.      Serve:

The server gets a request for the page. It can serve the pre-rendered page for the route form the cache or lazily pre-render the page for the route or serve app shell.

3.      Bootstrap and Swap:

When the page is loaded into the browser, the pre-rendered document is serving the browser. So, the user sees the page and its content. Then, Angular begins bootstrapping in the process. When it is completed, the pre-rendered document is replaced by the dynamically created Angular document.

4.      Replay:

It is something a developer can do, in the time between when the user is using the app and Angular is taking over.

The pre-rendered page is static HTML or CSS. So, the user interacting with such pre-rendered pages is catered in two ways.

One is to not render the interactive components or the users’ events can be recorded and replayed.

Down below, is an example of a progressive rendering of a page that Google docs does. There are interactive elements, but they are disabled until the page is completely bootstrapped.

Another way is to use preboot.js, it will record most user events on the pre-rendered page and then it replays the events against the browser-rendered app.

Forming pre-rendering strategies:

To deliver meaningful content to the user as quickly as possible, without significantly impacting time to interact. There are certain factors to consider like;

⚉ Number of pages

⚉ Content freshness requirements

⚉ Frequency of app deployment

⚉ Computation costs and availability

⚉ Developer ergonomics

⚉ How frequently is each page visited

⚉ Overall impact on time to interactive

Anatomy of a strategy:

Content strategy

It is basically, what should be rendered. It could be an only shell, a critical content with no secondary content or session-specific data or full content including session specific data.

What should be rendered is shown in a spectrum down below.

This spectrum ranges from generic (which fits everyone) to specific (which only fits users).

The same spectrum is now shown with a different scale, vertically, with simplest on the top, which has the full-screen rendering and on the bottom, it has partial screen rendering which requires less computations. From top to bottom, some example of web pages is given, ranging from simplest to less computations, accordingly.

Time strategy:

Take the same graph with the addition of another axis to it. On one extreme, there is build time. If the app is being built, the process of pre-rendering pages can be requested at that time, or it can be done for each request. In between, there is lazy request rendering, which allows the developer, to cache a user request so that when the same page is requested again, the cached version is delivered.        

There is a best run-time performance on the left end of the spectrum because there is less work to be done by the server at request time. But, on the right end, it is most dynamic.   

The same example is now plotted on this graph.

The bank statement is the request time because the recent info is required, always. The app shell will be at the bottom left because it is only a part of the screen. Product pages, news articles have a time component to them and may have some updates and edits that might be useful.

User strategy:

Different users have different content strategies like; a search engine. For them, only the critical content is rendered. Then there is a logged-in user, for them, show placeholder UI for user-specific content. Then there are anonymous users, for them, render a standard pre-cached anonymous version of the page. Lastly, there are international users, for them, render the page for the correct locale.

Example strategies:

1.      E-commerce product detail page

↠ It has 100 thousand products, along with 10 locales, user’s shopping cart widget and it is deployed daily.

↠ 100,000 products x 10 locales x 10,000 active shopping carts = 10 billion pages to build daily.

↠ What if the shopping carts are not rendered, instead just pages are generated for all products and all locales?

↠ 100,000 products x 10 locales = 1 million pages to build daily.

↠ Which is still a lot of pages to generate and it is likely that only a small fraction of those pages are accessed. The optimal strategy, in this case, can be the content.

Only the product details can be rendered.

Placeholder components for user-specific content (shopping carts in this case) can be rendered.

In the time strategy, a time render can be requested and then pass through cache for next request of the same product.

For the user, just render the product page for the user locale.

2.      Shopping cart page

This is not a common entry point for the site. It is not shared on social networks and it is not indexed by the search engines and the content changes very frequently, as the user uses the app, there is product change.                

The optimal strategy, in this case, can be;

⚉ Content- only cat specific app shell is rendered

⚉ Time- only the app shell can be rendered at build time

⚉ User- only render locale-specific app shell

TLDR:

↠ An app can have a single strategy or a number of strategies for different routes.

↠ The appropriate strategy may depend on the user or may not.

↠ Render can occur at any time; build time, request time or in parallel, it is really flexible and it depends on the developer, how they manage it.

↠ Page content is the biggest factor in determining the right strategy, what is going to deliver the best user experience.


Leave a Reply

Your email address will not be published. Required fields are marked *