[MUSIC PLAYING]
MINKO GECHEV: Hello, everyone.
My name is Minko Gechev.
I lead developer relations for Angular at Google.
Today I will share with you how to host a Modern Angular
application with Google Cloud platform.
We can render a single-page application implemented
in Angular or any other frontend framework
in three different ways.
Client-side rendering, where the browser receives an index HTML
file with reference scripts.
Once the scripts load, they render the application.
Prerendering, or static site generation,
is where at build time, we render an HTML file per route.
While the browser loads the HTML file for a route,
it renders it and loads the reference JavaScript.
The page becomes interactive once the browser
executes all the JavaScript.
Finally, there is Server-Side Rendering, or SSR.
SSR is very similar to prerendering,
with the main difference being that rendering
happens at request time by a rendering server.
Something in common between static site generation,
or prerendering, and client-side rendering,
is that in both cases, we only deal with static resources.
On the other side, in SSR, we need a server
to render the application and produce the application's HTML
at request time.
To demonstrate the different rendering strategies
and approaches and explore optimal deployment,
we will use the Kitty Cinema application.
Kitty Cinema is a catalog of cat movies.
We can explore a list of movies with their names, genres,
and ratings.
When we select a movie, we can get further details
about the synopsis, the cast, as well
to explore similar recommended movies.
For this application, we can use client-side rendering.
The deployment could be pretty simple.
First, we build the application and get its production assets.
After that, we can upload the assets using the Firebase
tooling.
Finally, the application will land on Firebase hosting
and be globally available via our CDN.
Currently, we can achieve this in a few different ways.
First, we can use the Firebase Node.js command line tools.
We can install them by using npm globally,
running npm i -g firebase.
After that, we can go to the Angular CLI Workspace directory
and initialize a Firebase project.
We can run Firebase in that.
After that choose Configure for Firebase Hosting.
Select a project you want to deploy,
specify a public directory--
in this case, the Angular CLI uses the output directory
dist/movies/browser.
After that, we're going to rewrite
all the URLs to index.html to make sure that Angular provides
client-side routing.
We're going to disable the deployment with GitHub.
We're not going to overwrite index.html at this point.
Since we have already built the application, as the next step,
just type firebase deploy and allow the tooling
to deploy the app.
Once all the files are in Firebase hosting,
click on the output URL to preview the app.
An alternative approach is using Angular CLI.
You can run ng add angular/fire to add Firebase integration
to your project.
Select that you'd like to set up ng deploy.
Select an account you will want to use, and next,
select or create a new project.
After that, select or create a new site.
And finally, just run ng deploy.
This command will build your project and upload
the static assets to Firebase hosting.
You can preview the result following the output URL.
Let us track step by step the sequence of events when
the user opens the application.
Once they enter the URL, their browser
will send a request to Firebase hosting
and download the index.html that is
the page for the application.
The browser will render the HTML and show a progress bar.
Next, the browser will download all the reference JavaScript
within index.html.
Next, the browser will execute the JavaScript.
Notice that at this point, the user
still sees only a progress bar.
Until the rendering completes, the page
will not be too useful.
At this step, Angular will take over
and render the user interface, making the page
useful and interactive.
For three out of the four steps, the user
only sees a progress bar.
That's not the best user experience.
Depending on the network connection speed,
it may take tens of seconds for the application
to show useful information.
In this particular case, we don't
need to make the application interactive immediately.
This is a content website, which means
that even if you only show the movie list,
the page will be still useful.
After that, we can load the associated JavaScript
and add interactivity.
Let's explore a more optimal deployment
strategy for the Kitty Cinema application.
This way, we're going to quickly serve
a useful page for the user and later on make it interactive.
We can select a suitable strategy
by identifying the different types of content we can render
and taking performance into consideration.
Ideally, we would want to prerender everything
at build time so that we can deploy the resources to a CDN
and deliver them from the closest location to the user
possible.
Often, pages depend on dynamic datastore or user session
information.
When we need to store personalized content on a page,
it becomes infeasible to generate it at build time.
We're going to consider two rendering strategies.
First one is prerendering, for pages
that don't change too often.
Serving them from a CDN will be cheap and really fast.
We don't have to do any work at request time,
and we can deliver them from the closest
to the user geographical location available.
For pages that need to show personalized content
or depend on a very frequently changing data source,
we can use server-side rendering.
It's usually more technically complicated and expensive
to serve such pages, because at request time
we have to run the application server, render the app,
and deliver the produced HTML to the user.
For prerendered content, we can use Firebase Hosting.
This is content delivery network,
allows us to serve the prerendered pages from a close
to the user location.
For server-side rendering, we'll use Cloud Functions.
They provide an on-demand execution environment
which allows us to handle individual requests,
render our single page application in Node.js,
and serve it to the client.
To prerender and server-side render our application,
we're going to use Angular Universal.
Just run in the terminal ng add @nguniversal/express-engine,
and Angular CLI will set everything up automatically
for you.
Earlier this year, Google Cloud announced
Cloud Functions Second Generation,
powered by Cloud Run.
This new version provides more powerful infrastructure
and advanced control on scalability and performance.
They offer longer processing times and larger
upgraded instances, with more memory and virtual CPUs.
To deploy our application, we'll use Firebase SDK
for Cloud Functions, which allows us to configure them
through code.
The Firebase SDK allows us to only run
Node.js in the function, which fits our goals really well.
In case you want to use other technologies,
check out this quick starting guide
for Cloud Functions in Google Cloud Console.
Let us have a look at two different pages in the app
and identify the optimal rendering
strategy for each one of them.
We have a generic page that shows the popular kitty movies.
We also have a page that shows user's favorites.
The generic page could be a good candidate
for prerendering if the popular movies don't change too often.
If they do change often, though, we'll
have to regenerate the application
and redeploy it very frequently.
In such a case, we might be better off
using server-side rendering for it.
The page showing the user's favorite movies
depends on the session ID, which means that we can't generate it
at build time, and we would have to use server-side rendering.
An approach where we use server-side rendering
for some of the pages in the app and prerendering for others
is called hybrid rendering.
With hybrid rendering, we can deploy the static resources
of the application-- for example, JavaScript images,
prerendered pages, et cetera--
to a CDN, for example with Firebase Hosting.
The pages containing the dynamic content
will render in Cloud Functions with server-side rendering.
Let us look into how to deploy an application to Firebase
Hosting that uses hybrid rendering.
To set up hybrid rendering, we can make a few adjustments
to firebase.json.
In this file, we can specify a predeploy command
to our function and its source.
Here we can specify that we want to build the Angular Universal
server before we deploy the function.
Next we can specify the hosting configuration.
We will set the public directory to dist/movies/browser.
We will use cleanUrls to strip the .html suffixes,
and also set trailingSlash to false.
Notice that we also add the star star
rewrites to the SSR function.
For optimal results, make sure that you also review
the best practices for caching.
Now let us compare the original client-side rendering
application with the latest deployment that
uses hybrid rendering.
On the left side, you can see the client-side rendered
application, and on the right side,
you can see the prerendered application.
Both are delivered from a CDN, but the client-side rendered
application needs to load JavaScript
before it can show any contents to the user.
Notice how the prerendered app loads and rendered
in half of the time needed for the client-site rendered
alternative.
Selecting the correct rendering strategy
can have a significant impact on your application's performance.
All right, that's most of what I had for you today.
Before we end, however, I want to share one more
thing you can do to further automate
the process of deployment of hybrid applications using
Incremental Static Generation.
We already mentioned in what cases we should
use prerendering and when we should
go with server-side rendering.
In short, if a page is user-specific or changes
very frequently, we most certainly
want to use server-side rendering for it.
We also discussed that we prerender pages at build time.
Let us look into incremental approach to prerendering.
To achieve incremental prerendering, or static site
generation, we can use automation via the APIs
that Firestore offers.
Let's suppose that we store all the content
that our static pages depend on in Firestore.
When we update the Firestore data,
we can get a Firestore change event
that triggers the Incremental Static Regeneration.
Once we regenerate pages that changed,
we can just deploy them to Firebase Hosting.
Deploying to Firebase Hosting purges the cache content
from the CDN, so we will want to balance
deploying on every change versus regenerating
static content periodically, while still falling back
to SSR.
That's everything I had for you today.
In this presentation, we had a look
into different prerendering strategies
for single-page applications and most optimal deployment
techniques for each one of them.
For more content, make sure you follow Angular on Twitter
and subscribe to the Angular YouTube channel.
See you next time, and happy coding.
[MUSIC PLAYING]