Troubleshooting Firebase Admin SDK Initialization on Firebase Hosting vs. Vercel: A Step-by-Step Guide

Deploying applications can often be a smooth process, but occasionally, you might run into some quirks that can take days or even weeks to resolve. This is a story about such an experience with Firebase Hosting and Vercel, and how I finally managed to get everything working.

My Requirement - I wanted to have multiple environments where i deploy my app. (Firebase and Vercel) so that i could maintain 1 site for experimentation and 1 for production.


The Problem

I spent close to 1.5 weeks trying to fix an error where the Firebase Admin SDK was not initializing on Firebase Hosting, even though it worked perfectly on Vercel. The issue was that when we deploy to Firebase Hosting, Google automatically initializes the Firebase Admin SDK with the details in the environment file. This automatic initialization was causing conflicts and preventing the SDK from initializing correctly.


The other major issue is debugging the deployment on firebase or vercel.

Firebase Hosting logs url.

For Logs of the Firebase hosting Go to

https://console.cloud.google.com/run?referrer=search&authuser=1&project=<project_id>

click on the function name typically ssr<project_id> and select logs tab


For Vercel

Use the below URL

https://vercel.com/<company name>-projects/<app id>/<build number>

Click on Logs to view the logs for the deployed app.

The Solution

After numerous attempts and a lot of troubleshooting, I finally managed to resolve the issue with a PowerShell script and some Code Revamp.


The solution was 2 pronged

  1. Deployment Changes
  2. Code Changes


Deployment Changes

This script handles the build and deployment process, ensuring that the environment variables are set correctly and that the Firebase Admin SDK initializes without issues.

Here is the PowerShell script that worked for me:

Write-Output "Starting build script ----------------------------------"
Write-Output "Incrementing version number ----------------------------------"
# : This is optional
# Call increment-version.ps1 to increment the version in package.json 
& ".\increment-version-number.ps1"

# Rename .env and .env.local if they exist
$envFiles = @('.env', '.env.local')
foreach ($file in $envFiles)
{
    if (Test-Path $file)
    {
        Rename-Item $file "$file.bak"
        Write-Output "Renamed file: $file"
    }
}

Write-Output "Building Next.js app..."

# Clean up existing build artifacts (if defined in package.json)
Write-Output "Delete .next folder ----------------------------------"
Remove-Item .next -Recurse -Force


# Clean up existing build artifacts (if defined in package.json)
Write-Output "Delete .firebase folder ----------------------------------"
Remove-Item .firebase -Recurse -Force


# Install dependencies
Write-Output "Install missing dependencies if any ----------------------------------"
npm ci


# Set DEPLOYMENT_ENV to firebase
Write-Output "Set DEPLOYMENT_ENV to firebase ----------------------------------"
$originalDeploymentEnv = $env:DEPLOYMENT_ENV
$env:DEPLOYMENT_ENV = "firebase"


# Build the Next.js app
Write-Output "Build Next.js app ----------------------------------"$env:GOOGLE_APPLICATION_CREDENTIALS = "path\to\firebase\credentials\firebase-prod.json"
npm run build

Write-Output "Build completed ----------------------------------"
Write-Output "Deploying to Firebase ----------------------------------"
try
{
    firebase deploy
    Write-Output "Firebase deployment successful"
}
catch
{
    Write-Output "Firebase deployment failed: $_"
}
finally
{
    Write-Output "Restoring .env files ----------------------------------"
    # Restore original .env files
    foreach ($file in $envFiles)
    {
        $bakFile = "$file.bak"
        if (Test-Path $bakFile)
        {
            Rename-Item $bakFile $file
            Write-Output "Restored file: $file"
        }
    }
}        

Explanation

  1. Incrementing Version Number: (Optional) The script starts by incrementing the version number in package.json using a separate script, increment-version-number.ps1.

# Read package.json and parse JSON content
$packageJsonPath = 'package.json'
$packageJson = Get-Content -Path $packageJsonPath -Raw | ConvertFrom-Json

# Increment the version number (assuming "X.Y.Z" format)
$versionParts = $packageJson.version -split '\.'
$patchVersion = [int]$versionParts[2] + 1
$packageJson.version = "$($versionParts[0]).$($versionParts[1]).$patchVersion"

# Update package.json with the new version
$packageJson | ConvertTo-Json -Depth 10 | Set-Content -Path $packageJsonPath

Write-Output "Updated package.json version to $($packageJson.version)"
        

  1. Renaming Environment Files: It then renames any existing .env and .env.local files to avoid conflicts during the build process. With this only the .env.production is picked up during the npm build
  2. Cleaning Up Build Artifacts: The script deletes the ".next" and ".firebase" folders to ensure a clean build.
  3. Installing Dependencies: It installs any missing dependencies using npm ci.
  4. Setting Deployment Environment: The script sets the DEPLOYMENT_ENV environment variable to firebase.
  5. Building the Next.js App: It sets the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of the Firebase Admin SDK credentials file and builds the Next.js app using npm run build. Update this for the correct path.
  6. Deploying to Firebase: The script deploys the app to Firebase using firebase deploy.
  7. Restoring Environment Files: Finally, it restores the original .env and .env.local files.


In addition to the PowerShell script, I also had to make some changes to the way the Firebase Admin SDK was initialized in my application code. Here is the updated code:


import credentialsDev from "@/path/to/your/firebase-dev.json";
import credentialsProd from "@/path/to/your/firebase-prod.json";
import { App, cert, getApps, initializeApp } from "firebase-admin/app";

const storageBucket = process.env.FB_STORAGE_BUCKET_NAME || 'gs://your-default-bucket.appspot.com';
const credentialsFileName = process.env.FIREBASE_SERVICE_ACCOUNT_FILE_NAME || 'firebase-prod.json';

let serviceAccount: any;
if (credentialsFileName === 'firebase-dev.json') {
    serviceAccount = credentialsDev;
} else {
    serviceAccount = credentialsProd;
}

if (!serviceAccount || !serviceAccount.private_key) {
    throw new Error(`Service Account private key not found for Environment -- ${process.env.NODE_ENV || process.env.NEXT_PUBLIC_ENV}`);
}

console.log('Existing apps:', getApps());

export function getFbAdminApp() {
    let fbAdminApp: App | undefined;
    const existingApps = getApps();
    if (existingApps.length > 0) {
        fbAdminApp = existingApps[0];
    }

    // If no app is initialized, initialize it using the service account
    if (!fbAdminApp) {
        try {
            fbAdminApp = initializeApp({
                credential: cert(serviceAccount),
                storageBucket: storageBucket
            });
        } catch (e) {
            console.error('Error in Firebase Admin Initialization: ', e);
        }
    }

    return fbAdminApp;
}        

Usage

const fbAdminApp = getFbAdminApp();
        if (!fbAdminApp) {
            throw new Error('Firebase Admin App not initialized');
        }
        const fbAdminFirestore = getFirestore(fbAdminApp)
        const fbAdminAuth = getAuth(fbAdminApp);
    }        


9. Environment Variables:

- DEPLOYMENT_ENV: This environment variable is set to firebase during the deployment process. It helps to differentiate between different deployment environments (e.g., development, production, firebase, vercel ) and can be used to conditionally execute code based on the environment.

- FB_STORAGE_BUCKET_NAME: This environment variable specifies the name of the Firebase Storage bucket. It is used to configure the storage bucket for the Firebase Admin SDK. If not set, a default bucket name ('gs://your-default-bucket.appspot.com') is used.

- FIREBASE_SERVICE_ACCOUNT_FILE_NAME: This environment variable specifies the name of the Firebase service account credentials file. It helps to determine which credentials file to use for initializing the Firebase Admin SDK (e.g., firebase-dev.json for development and firebase-prod.json for production).

10. Service Account Initialization:

The below code will be triggered in the Firebase Hosted site

 if (existingApps.length > 0) {
        fbAdminApp = existingApps[0];
    }        

while for others

    if (!fbAdminApp) {
        try {
            fbAdminApp = initializeApp({
                credential: cert(serviceAccount),
                storageBucket: storageBucket
            });
        } catch (e) {
            console.error('Error in Firebase Admin Initialization: ', e);
        }
    }        

The script checks the value of FIREBASE_SERVICE_ACCOUNT_FILE_NAME to determine which credentials file to use. Depending on the value, it loads the appropriate credentials (credentialsDev or credentialsProd). The script also verifies that the service account contains a valid private_key. If the key is not found, it throws an error, indicating a problem with the environment configuration.

This part of the code is required for deployment to other environments like Vercel.


11. Firebase Admin SDK Initialization:

By setting and using these environment variables, you can ensure that your Firebase Admin SDK is correctly configured and initialized for different deployment environments. This approach helps to avoid conflicts and errors, making the deployment process smoother and more reliable.

Please read thru the v10 of the firebase SDK which has changed the way we initialize the SDK as well as call the Firestore and auth objects Upgrade to Node.js SDK Admin SDK v10 (modular SDK) ?|? Firebase Admin SDK (google.com)


Conclusion

Deploying to Firebase Hosting can sometimes be tricky, especially when dealing with environment variables and the Firebase Admin SDK. However, with the right script and a bit of perseverance, you can overcome these challenges and get your app deployed successfully. If you find yourself in a similar situation, I hope this script and explanation help you resolve your issues more quickly.

要查看或添加评论,请登录

Pradeep Gudipati的更多文章

社区洞察

其他会员也浏览了