Twitter LogoFacebook Logo
Angular Server-Side Rendering with Cloud Functions
Learn how to turn your Angular Application into a Server-Side Rendering App and hosting it with Firebase Hosting and Cloud Functions.
By: King

Hello, in this tutorial, I will show you why and how to get your Angular project ready for Server-Side Rendering using Angular Universal and how to publish it onto Firebase using Hosting and Cloud functions.

What is the difference between a Normal Angular App vs. a Server-Side Rendered Angular app?

When you publish a normal Angular application on the web, it will get downloaded and executed in the browser. 


Each page is generated locally in the browser, based on the user’s actions.

When you publish an Angular application using Server-Side Rendering with Angular Universal, some of the contents of each page are generated on the server and then the rest will be in the browser.

This means that your application can render more quickly, and it allows your web pages to be visible on search engines and social media through meta elements. 

Normal Angular App

Because of how a normal Angular applications work, you’ll always get the same meta information displayed on any of the page you visit. The main HTML page of your application will get pulled initially from the server and then the application will generate the contents locally on the computer.

This causes search engines and social media sites to think that your site only has one page because they rely heavily on meta elements.

<!doctype html>
<html lang="en">
<head>
   <title>Codeible</title>
   <meta name="description" content="...">
</head>
<body>

  <app-root></app-root>

</html>

Server-Side Rendered App

With Server-Side Rendering, you’ll get a different HTML page for each page in the application because they are created on the server. This way, you can have different meta elements on each page.

Page 1

<!doctype html>
<html lang="en">
<head>
   <title>Codeible</title>
   <meta name="description" content="...">
</head>
<body>

  <app-root></app-root>

</html>

Page 2

<!doctype html>
<html lang="en">
<head>
   <title>Video Tutorials</title>
   <meta name="description" content="...">
</head>
<body>

  <app-root>....</app-root>

</html>

Begin Converting Your Angular App to SSR

To begin, open a new or existing Angular project in a code editor and add Angular Universal to the project. 


Run ng add @nguniversal/express-engine. 

ng add @nguniversal/express-engine. 

The command will create and update a couple of files in the project and that’s all you have to do. The application is now ready for Server-Side Rendering.

Publishing the App to Firebase

To publish the project to Firebase, go the angular.json file and change the outputPath for the build.


Replace the project’s name with functions and then scroll down until you see the server property and do the same.

"build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/functions/browser",
            "index": "src/index.html",
            "main": "src/main.ts",
            ...
          }

"server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist/functions/server",
            "main": "server.ts",
            "tsConfig": "tsconfig.server.json"
          },

By doing this, it’ll place our production files in a folder call functions when the project is built. 


Save the project, and in the terminal, use npm run build:ssr to build the project.

npm run build:ssr 

Once the project is built, you can see a folder call dist. Inside the dist folder is another folder call functions. And inside the functions folder are the production files.

Image from Codeible.com

Now locate the file call server.ts. This file is added to the project when you run the command to add Angular Universal. The code in this file is what the server will look at when generating the web pages. 

For the distFolder, replace the project name with functions as well. 

We need to do this because we need to tell the server where to look to find the files for our pages, and they are inside the folder call browser.

const distFolder = join(process.cwd(), "dist/functions/browser");

However, this path will only work if we’re testing locally on the computer. 


When we’re in the testing environment, the root directory is where the server.ts file is, and our website files are in the dist / functions /browser directory.

Image from Codeible.com

When we deploy the project to Cloud functions, the root directory will change and it’ll be the functions directory. The website files will no longer be in the path we defined earlier. 


To fix this, create a variable and use the production property in the environment object to check if we’re in test or production mode. 

If we are in the production environment, return browser because the web files are located in the browser folder. If not, return dist / functions / browser. 

const websiteFileLocation = environment.production? "browser" : "dist/functions/browser";

Replace the string for the distFolder with the variable and save the project.

const distFolder = join(process.cwd(), websiteFileLocation );

In the terminal, instead of using ng serve to start the local server, use npm run dev:ssr.

When the server is ready, launch your application on the browser. If your app is running correctly, we’re ready to test the code in production mode. 

Close the server in the terminal and change into the dist directory. 

cd dist

Then initialize Firebase hosting.

Initializing Firebase Hosting

firebase init

Image from Codeible.com

For the public directory, use the location of the browser folder and then configure as a single-page application.

Image from Codeible.com

Initializing Firebase Cloud Functions

Once Hosting is ready, initialize Cloud Functions. 

Image from Codeible.com

We want to install the dependencies.

Image from Codeible.com

When Cloud Functions is done, go to the package.json file for the project and copy all the dependencies. 


Then go to the package.json file for Cloud Functions and add them to its dependencies.

Cloud Functions package.json

"dependencies": {
    "firebase-admin": "^9.2.0",
    "firebase-functions": "^3.11.0",
    ...
    "@nguniversal/express-engine": "^11.2.1",
    ...,
    "express": "^4.15.2",
    ...,
    "zone.js": "^0.11.4"
},

Save the project and in the terminal, change to the Functions directory and install the packages.

npm i

Once the packages are installed, go to the index.js file and create a request function for Cloud Functions. 

const functions = require("firebase-functions");

exports.ssrapp = functions.https.onRequest();

Inside the parenthesis of the request, we want to use the express app function from the server.ts file. 

Server.ts

export function app(): express.Express {
  const server = express();
  ...
  return server;
}

When the project is built, this code will be generated in the main.js file inside the server folder.

To get the app function, go back to the index.js file and import the main.js file. then pass the app() function in the request. 

const functions = require("firebase-functions");

const mainjs = require(__dirname + "/server/main");
exports.ssrapp = functions.https.onRequest(mainjs.app());

Modifying the Firebase.json File

The last thing we need to do is to go to the firebase.json file and change destination to function in the rewrites array. This will tell Firebase Hosting to call a function from Cloud Functions instead of using an HTML page.


For the content, replace it with the name of the function you want to call. It has to match with the name in the index.js file or it’ll not work.

{
  "hosting": {
    "public": "functions/browser",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "function": "ssrapp"
      }
    ]
  }
}

Save the project and run firebase emulators:start in the terminal. 

firebase emulators:start

This will help us test our code in a Firebase environment before uploading it to Firebase. Copy the hosting address that is generated and paste it in the browser. 

localhost:5000

If your app is running fine, you’re ready to deploy it to Firebase. 

Open a terminal for the dist directory and deploy functions and hosting to Firebase.

firebase deploy --only hosting,functions


Sign In