DEV Community

Tom Bloom
Tom Bloom

Posted on • Edited on

Deploy a React Monorepo to Firebase with Multiple Environments

Deploy a React Monorepo to Firebase with Multiple Environments

Managing multiple React applications in a monorepo while maintaining separate staging and production environments can be challenging. In this guide, I'll show you exactly how to deploy a JavaScript monorepo with multiple React apps to Firebase, complete with proper environment separation and CI/CD integration.

The Setup: What We're Building

Let's say you have a monorepo containing multiple React applications managed by Lerna. In our example, we have:

  • πŸ“± Frontend app - Main user-facing application
  • βš™οΈ Admin app - Internal administration panel
  • 🎨 Design system app - Component library documentation

We need at least two environments:

  • Staging - For testing and QA
  • Production - For live users

We'll use separate Firebase projects for each environment, and within each project, we'll create different Firebase targets (sites) for each app in our monorepo.

Step-by-Step Firebase Configuration

1. Create Firebase Projects

For each environment, you'll need to:

  1. Create a Firebase project in the Firebase Console
  2. Initialize Firebase in your local repo using:
   firebase init
Enter fullscreen mode Exit fullscreen mode

2. Deploy Initial Content

This step is crucial and often overlooked:

firebase deploy
Enter fullscreen mode Exit fullscreen mode

Deploy an empty index.html file first. This step is essential because it activates the hosting service and allows you to create additional targets (sites) in your Firebase project.

3. Create Firebase Sites for Each App

For each app in your monorepo, create a separate site:

  1. Go to the Hosting section in your Firebase Console
  2. Click "Add another site"
  3. Create sites like:
    • myproject-app1-staging
    • myproject-app2-staging
    • myproject-app3-staging

Repeat this process for your production project.

πŸ’‘ Pro tip: While you can use the Firebase CLI to create sites, I recommend using the console to avoid accidentally overwriting your .firebaserc file.

Configuration Files

firebase.json - Define Your Targets

This file specifies how each target maps to your build folders:

{
  "hosting": [
    {
      "target": "app1",
      "public": "packages/app1/build",
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ],
      "rewrites": [
        {
          "source": "**",
          "destination": "/index.html"
        }
      ]
    },
    {
      "target": "app2", 
      "public": "packages/app2/build",
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ],
      "rewrites": [
        {
          "source": "**",
          "destination": "/index.html"
        }
      ]
    },
    {
      "target": "app3",
      "public": "packages/app3/build", 
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ],
      "rewrites": [
        {
          "source": "**",
          "destination": "/index.html"
        }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

I've added rewrites rules to ensure your React Router works properly in production.

.firebaserc - Environment and Target Mapping

This file maps your environments to Firebase projects and targets to specific sites:

{
  "projects": {
    "default": "myproject-staging",
    "staging": "myproject-staging", 
    "prod": "myproject-prod"
  },
  "targets": {
    "myproject-staging": {
      "hosting": {
        "app1": ["myproject-app1-staging"],
        "app2": ["myproject-app2-staging"],
        "app3": ["myproject-app3-staging"]
      }
    },
    "myproject-prod": {
      "hosting": {
        "app1": ["myproject-app1"],
        "app2": ["myproject-app2"], 
        "app3": ["myproject-app3"]
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Deployment Commands

Once configured, deploying is straightforward:

# Deploy to staging
firebase deploy -P staging

# Deploy to production  
firebase deploy -P prod

# Deploy specific targets only
firebase deploy -P staging --only hosting:app1
Enter fullscreen mode Exit fullscreen mode

CI/CD Integration with GitLab

Here's a complete GitLab CI configuration that builds and deploys your monorepo:

image: node:18.12.0

stages:
  - build
  - deploy

variables:
  # Disable source maps in CI for faster builds
  GENERATE_SOURCEMAP: false

# Build stage - shared by both environments
.build_template: &build_template
  stage: build
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
      - packages/*/node_modules/
  script:
    - yarn install --frozen-lockfile
    - NODE_OPTIONS=--openssl-legacy-provider yarn build
  artifacts:
    paths:
      - packages/*/build/
    expire_in: 1 hour

# Staging deployment
deploy-staging:
  <<: *build_template
  stage: deploy
  only:
    - develop
  environment: 
    name: staging
    url: https://myproject-app1-staging.web.app
  variables:
    REACT_APP_API_URL: "https://api-staging.myproject.com"
    REACT_APP_SENTRY_ENVIRONMENT: "staging"
  after_script:
    - npm install -g firebase-tools
    - firebase deploy -P staging --token $FIREBASE_TOKEN

# Production deployment  
deploy-production:
  <<: *build_template
  stage: deploy
  only:
    - main
  environment:
    name: production
    url: https://myproject-app1.web.app
  variables:
    REACT_APP_API_URL: "https://api.myproject.com"
    REACT_APP_SENTRY_ENVIRONMENT: "production"
  after_script:
    - npm install -g firebase-tools
    - firebase deploy -P prod --token $FIREBASE_TOKEN
  when: manual # Require manual approval for production
Enter fullscreen mode Exit fullscreen mode

Setting Up Firebase Authentication

To get your FIREBASE_TOKEN for CI/CD:

# Generate token (legacy method, but still works)
firebase login:ci

# Modern approach - use service account
firebase projects:list
Enter fullscreen mode Exit fullscreen mode

⚠️ Security note: Store your FIREBASE_TOKEN as a protected variable in GitLab CI/CD settings, not in your code.

Common Gotchas and Solutions

1. "Project not initialized" Error

If you get this error when trying to deploy to staging, it means you skipped step 2. You need to:

  1. Delete your firebase.json
  2. Run firebase init
  3. Deploy an empty index.html
  4. Create your sites in the console
  5. Restore your multi-target firebase.json

2. Build Optimization

For faster CI builds, consider:

  • Using --frozen-lockfile with yarn
  • Caching node_modules appropriately
  • Disabling source maps in CI (GENERATE_SOURCEMAP=false)
  • Using the legacy OpenSSL provider if needed

3. Environment Variables

Make sure your environment variables are set correctly for each environment in your CI/CD pipeline. React apps need variables prefixed with REACT_APP_.

Benefits of This Approach

βœ… Clean separation between environments

βœ… Independent scaling for each app

βœ… Atomic deployments - deploy apps independently

βœ… Easy rollbacks - each app can be rolled back separately

βœ… Cost-effective - Firebase hosting is generous with free tier

What's Next?

This setup gives you a solid foundation, but you can enhance it further:

  • Add preview deployments for pull requests
  • Implement blue-green deployments
  • Add automated testing before deployment
  • Set up monitoring and alerting
  • Configure custom domains for each app

Have you set up a similar monorepo deployment strategy? What challenges did you face? Share your experience in the comments below!

If you found this helpful, follow me for more DevOps and React content! πŸš€

Top comments (0)