Angular Universal with Firebase Dynamic Hosting
With the announce of the Angular-cli 1.3.0-RC2, it becomes really easy to create an Angular Universal project. For an Universal application, It is obvious that you need a running server to render the application at. For a standard Angular project we were using Firebase hosting and that was all. But Firebase is not a classic server so you cannot simply deploy the an Universal project and expect to see it running.
The question is how to serve an Angular Universal app without an actual server? Solution is simple; with Firebase Cloud Functions & Hosting
(Big thanks to David East who showed it is possible)
Angular Universal vs AoT
Before getting started to code, I would like to show the how the performance increases when Angular Universal is used.
First let’s look at the performance of an Angular application with AoT by using Lighthouse.
In the AoT version, first meaningful paint metric is 1.780ms
In Universal version, the metric decreases to 1.160ms. The difference is almost 600ms which is precious amount of time when it comes startup performance.
Angular Universal Setup
Let’s start to code by creating a new project with angular-cli 1.3.0RC2.
ng new angular-universal-with-firebase-hosting
By default angular-cli will generate a standard project. So we need to change project settings to get an app with server-side rendering.
We won’t cover how to convert standart project to Angular Universal, it is described here very well.
After creating the project, you should login to Firebase Console and create a new project if you didn’t create one yet.
Then come back to command line, go to project folder and initialize firebase.
Which Firebase CLI features do you want to setup for this folder?
It will ask the required features, “Functions” and “Hosting” must be selected, “database” is optional
Then proceed by selecting the following options.
Do you want to install dependencies with npm now? (Y/n): N
What do you want to use as your public directory? (public): dist
Configure as a single-page app (rewrite all urls to /index.html): Y
✔ Firebase initialization complete!
Now we have folder structure like this:
On the classic method, Firebase hosts the source files under /dist folder and serves them when there is request.
We want to render the application on the server. To do that, we will create cloud function to serve the application.
Therefore we will put our build files under /functions.
This section has 9 steps. It is a bit long but it will be worth at the end.
1. Change the build folder
Go to .angular-cli.json and update as “outDir”: “functions/dist-server”
2.Build the project
ng build --prod --app 1
3. Copy “index.html” to under “functions/dist-server”
copy src/index.html functions/dist-server
4. Install dependencies to functions
Functions need dependencies to serve Angular Universal. So let’s add the following dependencies to it.
Open ./functions/package.json and update:
5. Create the function that serve Angular
We create an express app and make it serve by firebase at the end by exporting it as “ssr”.
functions.http.onRequest(app) // when there is an HTTP request call
Warning: Don’t forget to change “main.61c8194551162576f43c.bundle” with your bundle file name.
7. Tell the Firebase Hosting to serve Cloud function
We need to state that we want to serve our cloud function when there is an http request on a specific route. So we should go to firebase.json and update it.
Now For every URL our function will be called.
8. Arrange ./dist folder
Since we don’t use actual Firebase Hosting service we must delete dist/index.html otherwise the app will always serve index.html.
it is not possible to deploy app if dist folder is empty, so we create dummy file named custom.css
9. Deploy the application to Firebase
Now everything is ready.
Last and easiest step is deploying our project to Firebase.
=== Deploying to 'angular-universal-1fc26'...
i deploying functions, hosting
i functions: ensuring necessary APIs are enabled...
i runtimeconfig: ensuring necessary APIs are enabled...
✔ functions: all necessary APIs are enabled
✔ runtimeconfig: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (7.14 KB) for uploading
✔ functions: functions folder uploaded successfully
i hosting: preparing dist directory for upload...
⚠ Warning: Public directory does not contain index.html
✔ hosting: 1 files uploaded successfully
i starting release process (may take several minutes)...
i functions: updating function ssr...
✔ functions[ssr]: Successful update operation.
✔ functions: all functions deployed successfully!
✔ Deploy complete!
Hosting URL: https://angular-universal-123456.firebaseapp.com
Go to hosting URL and see your server-side rendered application.
Note: In this post we only cover a single route. But it is easy to implement multiple routes to this architecture. This post will be updated near future with an actual application multiple routes
Angular Universal has many advantages such as better performance, better SEO and availability of site preview for Facebook, Twitter etc… With cool features of Firebase, it becomes available to have an Universal app without so much effort. Hope that this article will help you to make use of this advantages easily.
Please recommend if you find the post helpful so it can reach the other people as well.