CI/CD with GitHub Actions — React Monorepo to Firebase Hosting

Sau-rav Adhikari
May 4 · 9 min read
React Mono Repo -> Firebase using GitHub Actions

In this article, we will be exploring how to deploy and host our React app (Monorepo) to Firebase hosting with different sites for each package using targets.

I am writing for Monorepo, however, you can follow the exact same procedures for a monolithic repo too, with some changes in the configuration.

Well, things don’t end there, we will also set up CI/CD (Continuous Integration/ Continuous Deployment) pipeline using GitHub actions to automate the process. So, for simplicity, I am breaking this entire article into 3 sections.

  1. Setup React Monorepo app (Optional)
  2. Setup Firebase Project
  3. Setup GitHub Actions

Setup React Monorepo app (Optional)

If you already have a Monorepo app you can skip this section. However, if you want to jump right in and get started then you can use my repo for ease. I have already set up a basic barebone Monorepo for this.

Clone the repo and install the dependencies, after that everything should be set up for you.

You can delete firebase.json .firebaserc and .github/workflows if you want a complete fresh start.

# To install dependencies or you can use "npm" as well
yarn install

To make sure the app is working correctly run it using the following command in your terminal.

yarn start

This will run 2 simple packages, “admin” & “user” and you should see the following tabs open in your browser.

User Package
Admin Package

Our Monorepo app is now set up and is running, let’s move on to the next section.

Setup Firebase Project

Let’s set up and configure our firebase project so that we can later deploy and host our app. For this, sign in to your Firebase console and set up a new project.

Step 1: Create a project

You can choose to set up analytics or not, it's up to you. After successfully creating your project, you will be redirected to “Project overview”. Go to hosting and press “Get Started”.

Step 2.1: Setup Hosting

You will be shown the setup guide for Firebase hosting.

We will automate the deployment using CI/CD pipeline in the next section. For now let’s make sure we are able to deploy.

Setup 2.2: Setup Firebase CLI

After installing “firebase-tools”, login to your firebase console from the CLI and then initialize your project using the commands shown below.

Setup 2.3: Initialize your project

After you enter firebase init you will be shown the following menu:

`firebase init` menu

Currently, I am only going to use “Hosting” only, but you can select all the features that apply to your app.

Firebase Project Setup

Since we have already set up a project, go with “Use an existing project” and select your project. Also, for “Hosting setup” you can configure as per your requirements since we will be re-writing most of it so I just entered random info.

Firebase Hosting Setup

The configuration info that is generated will look like the following.

firebase.json

public : This is the folder from which files will be served.

rewrites : We are redirecting every route to ./index because we are creating a single-page app.

And our project information will look something like this.

.firebaserc

However, things do not end here for us. Since we are trying to deploy our two different packages to different sites we will need to modify these configuration files. For this, let’s go back to the “Firebase console” and set up sites. In the “Hosting”, scroll down and go to the advanced section.

Hosting advanced

Let’s add two sites for the “admin” and “user” package.

“{{SITE_NAME}}.web.app” be used to access your package/app.

Adding sites for the packages

Now let’s update our configuration file to link our packages with these sites.

$ firebase target:apply hosting admin monorepo—admin
$ firebase target:apply hosting user monorepo—user

This will update the .firebaserc file with the following contents.

Here “targets” refers to “Deploy targets” which are identifiers for Firebase resources in our Firebase project.

admin” & “user” is the target name, which we will specify in our firebase.json . “monorepo — admin” & “monorepo — user” are the names that we gave to our sites in the “Firebase console”.

Now let’s update our firebase.json to serve their respective build files to their sites. We will update the “hosting” attribute to an array of objects rather than an object since we are trying to host multiple sites. Notice, here we have added a new “target” property that matches the target name in .firebaserc , also we have updated our public directory to the respective build folder of the projects.

Now, we can finally deploy our app to Firebase hosting. In your console, type the following command and your app should be up and running.

Step 2.4 Firebase deploy

Congratulations, you have finally deployed your React Monorepo app to Firebase hosting with multiple sites.

Setup GitHub Actions

Well, now it’s time to set up our CI/CD pipeline.

You can do this directly from your GitHub repo’s actions tab.

Setting up Actions form GitHub

Another way to set up actions is to manually create the “workflows” folder inside the “.github” folder in your root directory.

Inside workflows, we specify our CI/CD pipeline workflows. Let’s look at an action generated by GitHub. GitHub contains many actions for us to use, this is the most basic one.

[3] name: The name of our workflow which GitHub displays on our repository’s actions page. If we omit this field, GitHub sets the name to the workflow’s filename.

[6] on: This contains the name of the GitHub events that trigger our workflow. Currently, we trigger this workflow when new commits are pushed or pull request is opened on “master” or “main” whichever is your default branch.

[workflow_dispatch] You can manually trigger workflows with the new workflow_dispatch event. You will then see a ‘Run workflow’ button on the Actions tab, enabling you to easily trigger a run.

The manual trigger for workflow

[17] jobs: A workflow run is made up of one or more jobs that run in parallel by default. To run jobs sequentially, you can define dependencies on other jobs using the job’s id and needs keyword.

Here we have specified a “build” job that runs on Ubuntu and follows the “steps”. You can run your job on multiple OS environments as well.

In each step, we can reuse existing actions or write our own commands.

[26] uses: Selects an action to run as part of a step in your job. We can pass parameters to the actions using with which we will see later.

“You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image.”

[29,33] Here you can see how you can run a one-line script or multi-line script in your workflow.

If you commit and push then you should see the workflow running in your GitHub repo’s action tab.

GitHub Actions Tab

You can get detailed information about each step of the job as well, just click on the commit message and select the job, you will be shown logs of all the steps.

Now, let’s set up our own workflow to build and deploy to Firebase. We will be setting up the following workflow for our project to build & deploy. You can add more steps like “linting”, “removing other files except for ./build” etc. as per your needs.

Workflow example

Currently, we will trigger our workflow for any push on the “master” branch but you can set up workflows to be triggered as per your project needs. For example: Run only build on other branches except for “master” since we won’t deploy just any branch.

Your project might require env variables to run first let’s add them to our repo. Go to the “settings” tab and select “Secrets”.

If you are deploying for multiple environments like “Production”, “Staging”, “Development” etc. you can also use “Environments” to manage such scenarios. However, if you are using private repo “Environments” is only available for the GitHub enterprise plan only.

We will just be setting up repo secrets for now.

GitHub repo’s Settings Tab

In the secrets section, click the “New repository secret

GitHub repo’s Secrets
Adding GitHub repo secrets

Also, we need to add a “ServiceAccount” with permission to deploy to firebase hosting, you can set that up manually, or let’s do it from Firebase CLI. Since we have already set up hosting for our project we need to set up only the GitHub actions part for this type the following command in your root directory.

$ firebase init hosting:github
Firebase Hosting with GitHub actions

Delete any workflow files generated by this since we will have our own workflow file to deploy to Firebase hosting. Now, you should see the following secret added to your repo secrets.

After you’ve set up your “Secrets” let’s start configuring our workflow file.

[ #One]: We are checking out our repo. This will also set the working directory to our project's root directory.

[#Two]: We set up a cache directory for our yarn dependencies and keys for restoring them if there is no difference between previous dependencies and current ones by comparing the yarn.lockfile. This action will Post check dependencies to our cache when the job runs successfully. This will reduce our build time since we will not need to install dependencies every time.

You can see here I am reusing actions/cache@v2 , one good thing about GitHub's Actions is that there are numerous actions written and shared by the community. You can see I am providing various input parameters using with.

[#Three]: Check-in cache if dependencies exist, if it does not exist install the dependencies otherwise do nothing. Your first build will usually be slower, but your subsequent builds should be comparatively faster because of this.

[#Four]: We create .env files for both our packages. Notice how we are changing the directory before creating files and also using ${{secets.SECRET_NAME}} to access secrets we previously added to our repo.

[#Five]: Currently I don’t have any tests written for these packages but you can write some tests and update the scripts and run them. We should always make sure our tests are running successfully before building our project. No point in deploying faulty code to production.

[#Six]: Here, we build the project. This will generate /build for both of our packages which we have already specified as our public folder in firebase.json file. Now, our project is ready to be deployed.

[#Seven]: We deploy our code to Firebase hosting’s live channel. You can also host your code to the preview channels if you want.

Now make some change and push your commit to the masteror main branch whichever you have configured. If everything is working fine, your code will be deployed to Firebase

Build & Deploy workflow

I hope this article has been helpful to you. Keep learning & Stay Safe. 😁

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium