Pre-Rendering Strategies – Jeff Cross – AngularConnect 2017
Articles,  Blog

Pre-Rendering Strategies – Jeff Cross – AngularConnect 2017


JEFF: Hello. Thank you. I’m not going to try
my English accent here. If you want to hear it, talk to me in person because it is a private
matter! I’m here to talk about pre-rendering strategies. I’ve been reluctant to call it
universal because I think pre-rendering is a better term. If there is something interesting,
I will tweet the link. Quick bit, I used to be on the Angular team. For the last year
on the Angular team at Google wag the tech lead of the Angular mobile team. We focus
on a lot of things regarding progressive web apps, and making apps generally load fast.
So a lot of Angular focuses on the load time performance but we focused on the loading
performance and make the perceived performance better for all users. Part of that was universal
and pre-rendering which aim going to talk about today. I’m the co-founder of nrwl. Victor
is a core team at Google. We’ve got a website and a blog at:
And everything we do is Angular-related. We’ve got a lot of deep Angular content on our blog,
and books we’ve written going deep into Angular content. We’ve recently released an open-tool
kit that makes it, adds a lot of conventions and tools and libraries that make it easier
to build large applications of Angular that I would recommend checking out. So, as much
Angular 4, pre-rendering is a first part of Angular, having Angular/server, bringing with
it benefits being a first-part of Angular, it is kept up to date with Angular, it’s is
well-reviewed internally at Google, gone through lots of audits and validation of its design.
Lots of things about being first class. When I say pre-rendering I’m talking about the
ability for you as a developer to develop HTML before it gets to the browser before
it is fully rendered and all your application components rendered on the page so users can
see your page while Angular warms up inside the browser and bootstraps. When something
is pre-rendered on the right compared to an empty page. Sometimes, it is asked is this
the same thing as the AoT compilation? The AoT compiler produces optimised JavaScript
so your code can be smaller and faster whereas pre-rendering generates an HTML document.
Pre-rendering exists to satisfy these three priorities: we want apps to be fast loading
and perceived as fast because they’re actually multiple phases or multiple steps in how an
app is loaded and displayed to the user. We want to have more meaningful content earlier
on. We want apps to be scrape-able by social-sharing scrapers. We want them to be crawlable by
search bots. We’ve got things built in for quick downloading and AoT which shouldn’t
be new to anybody. Build optmiser makes tree-shaking more powerful. Lazy loading, and the opportunity
for improvement we want to seize with pre-rendering is the dead time between when your HTML has
loaded and your app has done bootstrapping in the browser. There are two metrics that
we want to focus on when we talk about optimising that time. The first one is the time to first
meaningful paint – in other words, when the user can see content that is meaningful to
me. The second is time to interactive or when I as a user can do something with the application
and it will respond to me. So, to give a little bit of a visual, without pre-rendering, your
load may look like this, where the loading takings the time on the blue, and bootstrap
on the green. The bootstrap time, once the HTML is loaded if all I have is the text in
there, I may just see loading, up until the time Angular is done bootstrapping and my
application is interactive. With pre-rendering, we can take some of the same graph and shift
the time by sniffing the amount of time because we can have content ready to look at while
Angular then warms up in the browser and evaluates and finishes bootstrapping and becomes fully
interactive. So to make our application scrape-able, we care about things like Facebook and Twitter.
A user shares a link to my content on their Twitter feed, we want the metadata like title,
description, and content – I forgot to spit out my gum! We want that to show up on their
feed. These scrapers prefer specific metatags. A lot will look at the body. They see the
content of your page and seek canonical URLs, or if this page can be accessed from different
URLs, they want to see the canonical URL, the correct single URL so they can create
content. These scrapers don’t run your application and execute JavaScript and see what the final
state of your JavaScript running the application is. So, for example, if I want to share a
blog article, I want it to look like this, where I’ve got the description, the title,
everything like that, the image for the article shows up correctly in my Twitter feed. And
how these social networks make this possible is they have their own metatags defined. Facebook
has tears, and Twitter has a card protocol. For these, this actually doesn’t require a
lot of pre-rendering to make right. We want our apps to be crawlable. They want to see
a title, a meta description, and also canonical URLs. They would look more at page content,
so what is in the body of the page, what are users actually going to see? They want to
know for certain that what they’re drawling and indexing is what you’re showing to users.
What is unique about these compared to social scrapers is they will execute JavaScript.
Google bot has got more advanced at this. So there are some limitations, like they run
an older version of chrome when they do it so it doesn’t have the APIs, and there are
lots of other nuances about how they crawl your web application. And they still recommend
pre-rendering to make sure if you care about search engines, and optimisation, it is important
to you, it is a good idea to pre-render in case the Google bot can’t fully render your
JavaScript page. So those are the priorities. We want it to be fast, scrape-able, and crawlable.
Before I start talking about how we can approach this, let’s talk about the process of what
it actually means to pre-render, and what it actually looks like for you as a developer.
There are four stems: you render something, you serve it, you bootstrap your application,
and you can replay events. So, for render, it is really this simple: Angular platform
server provides this render module factory supervision that you pass it, a — renderModuleFactory,
like the URL you want to render. It spits out a full string of HTML on the other side
that you can do what you want with. There are misconceptions with pre-rendering, probably
the biggest one being that you must run it on a server, or that you must run it when
you run a request on the server. This isn’t true. It is just a function you call. Any
place you can call this function, you can pre-render something. Another is that you
must render the page exactly as it will appear after bootstrap. So that you have to render
the full page, and everything on the page, every component. And that you have to apply
the same strategy across your entire application. If you’re rendering a route, every part of
your application must be pre-rendered, so every part must support re-rendering. That’s
a quick overview of the render process. Then you have to serve your page. A server gets
a request for the page. You can either serve the pre-rendered page from your cache, or
you could lazily pre-render the page foreign the route if it hasn’t been pre-rendered,
or serve an app shell, like a shell of your application with the header and footer. These
are like a summary of things you could do. Serving is pretty simple. It is also been
pre-rendered be you have to give it the right thing. Then bootstrap. So when you load your
page in the browser, the pre-rendered document is serving the browser, so the server sees
your page with whatever content you’ve already served. Angular begins bootstrapping in the
browser, so the scripts evaluate, Angular starts doing its thing. When it is done and
ready to take over, the pre-rendered document will be removed and the new dynamically created
Angular document will replace it. So, that brings us to the last part of it which is
what happens as in between time as the user using the application and Angular takes over?
There are some things we can do. We can replay user events. Since the pre-rendered page is
just static HTML and CSS, like no JavaScript event listeners or anything ready at that
point, what happens if the user interacts with the page? If they start typing an input,
what will be the effect of that? There are a couple of ways you can account for users
interacting with your pre-rendered page. One is you could not render inactive components
which was actually my preferred strategy in most cases, or you could record and replay
user events. So, this is actually a pretty good example of progressive rendering of a
page that Google docs does where they render the page, and they’ve got interactive elements
there but they are disabled for now until the page is bootstrapped and ready to interact.
For me as a user, I see that I will be able to do these things but it’s obvious that I
can’t I felt do it, and when I’m able to, I can. That’s one way to get around it. There’s
another option. Jeff Whelply has created a library along with other developers recording
most events on the pre-rendered pages, and then once the page is bootstrapped in the
browser with Angular, it will replay those events to try to get the UI to the same state
that it was beforehand so the user can interact sooner and have a seamless transition after
the faction. There are some gotchas with that since there are not validators and other side
effects applied to the page. It is possible the user can’t do things that are allowed
by the Angular application so might see some things happen when it takes over, so it is
important to be conscious of how you use it, and use it in as few cases as you need to,
or you can really deliver a better experience with it. So, that’s basically how pre-rendering
works. If any of these things are important to your application which I don’t know too
many applications that don’t value these things, then it is worth considering pre-rendering,
at least for part of the application. So, to get started on that path, the main point
of this talk is to talk about some strategies or how to think about pre-rendering strategies
that make sense for your application. Pre-rendering our goal is to deliver meaningful content
to the user as quickly as possible while not significantly impacting the time to interactive.
We want users to see things, but we also don’t want to delay the time to interactive where
they can use the app too much. There are a lot of factors that go into your strategy.
One is how many pages do you have? How fresh must your content be to be useful? How often
am I deploying my application? How much computation costs do I have? Like these machines and jobs,
how many can I delegate to to do re-rendering? How much work is it for me as a developer
to develop my application in a way that can be he pre-rendered? How often is each page
visited? There are certain pages of your site that are visited more than others. I don’t
typically link into my shopping cart page on Amazon, I go to amazon.com. Not many people
are sharing links to a shop cart. Your overall impact on time to interactive is an important
thing. As we make the first step faster we don’t want to have a heavy cost on how long
it takes for the app to become fully interactive, especially of concern is if you’re doing the
pre-rendering at run time as the request is being performed, obviously, doing a little
bit of computation on the server that will take a little bit of time. You want to make
sure that is not delaying the time to interactive significantly. I’ve broken the anatomy of
the strategy in in three categories. It has a content strategy, a time strategy, and a
user-specific strategy. Let’s look at what the content strategy is. This is what should
be rendered on a route. There are options here. You could render a shell, so you could
have a global app shell for every page of your application that has the header and footer
and that’s all you show. That’s the easiest thing to do. Or you could just render the
critical content of a page. You could render the article or ignore the other components
and widgets on your page which is good for search engines and some other use cases. Then
the last kind of extreme-use case is we could render everything on the page, including session-specific
data for that user at that moment. So, when we think about what should be rendered, here’s
a scale. Like the app shell, you’ve got something minimal, and, on the middle, you’ve got critical
content where there are interactive elements that are disabled when they are there, so,
when the page bootstraps, it is not a big jar, or jarring transition to go to that,
it is fluid. In that the end of the spectrum, we have it where we have full session data,
and everything for that using that looks pretty much how it will look as angular has done
bootstrap. This is a spectrum from generic to specific, where generic fits everyone,
and specific where it fits the user. If we put simplest at the top, rendering the entire
screen is simpler because you don’t think too much about what is rendering, but partial
screen which is less computation, so, if you’re doing that at runtime, the less work you have
to do, the better. Some example of where pages in real life would fall on the spectrum are
like a bank statement. You want it to be re-rendered each time because you want the exact moment
of your account balance, you don’t want to see yesterday’s account balance. Blog posts
are things that maybe get updated with comments or edited to re-render on the fly. At the
bottom, a settings page deep in my application that I probably don’t even need to pre-render
it, I can just serve an app shell and render it on the front end because it is not a frequently
visited page and certainly not a deep-linked page. Then we have the time component of our
strategy. If we take our same graph and we add another axis to it, we’ve got when the
page is rendered. On one extreme, we’ve got build time, so as I’m building my application,
the CI process or locally, I can decide to pre-render pages that I want to at that point,
where object at the other end, request the page that comes in and render the page in.
Request time, you’re obviously doing work in the critical path of serving a request
which makes it hard to deliver quickly, and then in between, you’ve got in betweens: you’ve
got lazy request rendering with of the next time it is requested, you can cache it, and
the next time it’s requested, you can deliver the cache person. Then you have a batch or
cron job that is rendering pages as your site is being served and you can check if that
has been pre-rendered from that cache when you’re serving, but it doesn’t have to render
everything. So we’ve got the best runtime performance on the left end of the spectrum
because there’s left work to be done by the server at request time but on the right-hand
side, we’ve got the most dynamic, so it is a little bit easier because it is fully dynamic
and I don’t have to do much thinking about optimising it. So plotting the same applications
on this graph, the bank statement is the request time because you always want the most recent
info, but like an app shell would be the bottom left because it is only part of the screen,
and you could do it at build time, and you should do it at build time. Product pages,
news articles, those are things that have somewhat of a time component to them and may
have some updates or edits that you want to grab. The last aspect is the user. So, different
users will have different needs of what they need to be shown, and some of it is relevant
for what should be pre-rendered. Like a search engine is one of the users, for example. For
them, you probably want to render just the critical content, and you probably want to
render it often. So that it is fresh. And then we have logged-in users. For them, there’s
probably more interactive or more user-expensive elements on the page. A lot of the time, it
is not necessary for those to be pre-rendered. If I’m not looking at a product page to see
the items in my cart or things that might be on the page, I could have a placeholder
UI for those. Then we have anonymous user who could probably just render a pre-cached
anonymous page for every anonymous user, and international users, we want to render the
correct locale for they remember. That’s how strategies work. I want to talk about a couple
of use cases where you would consider to mix-up strategies for different routes in your application.
If we considered an e-commerce product detail page, it’s got 100,000 products, ten locales,
this dynamic widget on it and lets me offer to pay for my cart. We do daily deploys of
this. If we have these characteristics, that means it would be ten billion pages to build
each day, assuming we did it at build time and building every product. That’s a lot to
generate in a build step. I don’t know how many data centres you could be throwing at
it. What if we said we don’t need to render the shopping cart data, we can replace that
with an element that is inactive, and we just generate the pages for all products for all
locales? That would take it down significantly to where we have a million pages to deal with,
but that’s still a lot to generate, and it’s likely that only some of those product pages
will be accessed every day. You know, some products are more popular than others. So,
what the optimal strategy in this case would be, the content we could just render the product
details when requesting the product detail page, and render placeholder components for
the user-specific content like the shopping cart. The time we could render request time,
and then we could have a pass-through cache so that after it has been requested, I cache
it, and then next time, a user requests the page, it is also cached. There are other ways
you could do it. This is just one example. You could actually get more complex and say
that we know what the most popular products are, so when we build it, we will kick off
a product when we start rendering it so there will be a cache and ready to serve. For the
user, we render a product page for their locale, up so we can share that rendered page for
all users at this locale. So let us look at another example: we’ve got a shopping cart
page as our shopping cart. This isn’t a pretty common entry point for the site – most people
buy stuff and end up at the shopping cart. I don’t share this link on Facebook. It is
not indexed by search engines because why would it be? The content changes pretty often,
as the user uses the app and products change. So the optimal strategy in this case is for
the content, all we should render is the app shell. We could render the full cart if we
wanted to at full-time but not a lot of value there. Users aren’t directly landing at this
page. They’ve got the application warmed up at the time they land at the shopping cart
page. And for a time, we could just render the app shell at build time, so doing it on
the server, it is quick to do it at build time to pre-render the app shell. And we will
have the cart-specific app shell in this case. For the user, we want to have the right locale
for them, so if we support ten locales, we would have ten cart app shells and select
the right one to serve to them. So to conclude, application could have a single strategy for
the whole application or mixed and matched different strategies for different routes
and users. The stage may depend on the user or may not. Rendering can occur at any time.
Build time, request time in parallel, it is flexible enough to you how to manage it. The
content of the page is the biggest factor in determining what is the right strategy,
what is going to deliver the best user experience. So that is a little bit of a high-level how
to think about strategy, how to think about integrating pre-rendering in your application.
If you want to get started, right now, there’s a great github Wiki that walks about how to
use Angular CLI with universal. By the time people watch this talk, there may be other
docs that are better than this. It’s good to Google Angular CLI universal. Maybe I will
write a blog page and get all the traffic. But, yes, check it out. It is a really well-done
story. I’ve seen a lot of people go through it without problems. I’ve done it myself.
Check it out. Get started, and you will see how I – I think it’s pretty easier. You will
see how much easier it’s gotten in the last year. Thank you. I will tweet these slides.
If you follow me on Twitter. Grab me, send me an email, or I’m happy to chat. Thank you.
[Applause]. ED: Thanks, Jeff. We’re back here in a few
minutes talking about animations, so see you in about five minutes.

Leave a Reply

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