To add Firebase to an existing web project, create a Firebase project in the Console, register a web app to get your config object, install the firebase npm package, and call initializeApp() with your config. Store config values in environment variables for security. The modular v9+ SDK uses tree-shakeable imports like getFirestore and getAuth, keeping your bundle size small. Works with React, Next.js, Vue, and any JavaScript framework.
Adding Firebase to an Existing Web Project with the Modular SDK
This tutorial walks you through connecting Firebase to an existing JavaScript or TypeScript project. You will create a Firebase project, register a web app, install the SDK, and write a reusable initialization module using the modular v9+ syntax. The guide covers environment variable setup for security and shows how to import individual Firebase services like Firestore, Auth, and Storage.
Prerequisites
- A Google account to access the Firebase Console
- An existing web project with npm/yarn initialized (package.json exists)
- Node.js 18 or later installed
- A code editor like VS Code or Cursor
Step-by-step guide
Create a Firebase project in the Console
Create a Firebase project in the Console
Go to console.firebase.google.com and click Add project. Enter a project name, optionally enable Google Analytics, and click Create project. This takes about 30 seconds. Once created, you land on the project dashboard where you can add apps and enable services.
Expected result: A new Firebase project is created and visible in your Firebase Console dashboard.
Register a web app and copy the config
Register a web app and copy the config
From the project dashboard, click the web icon (</>) to add a web app. Give it a nickname (e.g., your project name). You do not need to enable Firebase Hosting at this step. After registering, Firebase displays your config object containing apiKey, authDomain, projectId, and other values. Copy this entire config object.
1// Firebase displays this config object after registering your app:2const firebaseConfig = {3 apiKey: "AIzaSy...",4 authDomain: "your-project.firebaseapp.com",5 projectId: "your-project-id",6 storageBucket: "your-project.appspot.com",7 messagingSenderId: "123456789",8 appId: "1:123456789:web:abc123",9};Expected result: You have a firebaseConfig object with your project's specific values.
Install the Firebase SDK
Install the Firebase SDK
In your project root, install the firebase npm package. This single package includes all Firebase client services: Firestore, Auth, Storage, Analytics, and more. You import only what you need thanks to the modular v9+ architecture.
1npm install firebase23# Or with yarn:4yarn add firebaseExpected result: The firebase package appears in your package.json dependencies.
Create a Firebase initialization module
Create a Firebase initialization module
Create a file (e.g., src/lib/firebase.ts) that initializes Firebase and exports the service instances you need. Use the modular v9+ import syntax which enables tree-shaking — your bundler only includes the Firebase code you actually use. Initialize each service once and export it for use throughout your app.
1// src/lib/firebase.ts2import { initializeApp } from "firebase/app";3import { getFirestore } from "firebase/firestore";4import { getAuth } from "firebase/auth";5import { getStorage } from "firebase/storage";67const firebaseConfig = {8 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,9 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,10 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,11 storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,12 messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,13 appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,14};1516const app = initializeApp(firebaseConfig);1718export const db = getFirestore(app);19export const auth = getAuth(app);20export const storage = getStorage(app);Expected result: A firebase.ts module exports initialized db, auth, and storage instances ready for import anywhere in your app.
Set up environment variables
Set up environment variables
Store your Firebase config values in environment variables instead of hardcoding them. Create a .env.local file in your project root for local development. For production, set the same variables in your hosting platform's environment settings (Vercel, Netlify, etc.).
1# .env.local (Next.js)2NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSy...3NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com4NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id5NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com6NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=1234567897NEXT_PUBLIC_FIREBASE_APP_ID=1:123456789:web:abc12389# For Vite projects, use VITE_ prefix:10# VITE_FIREBASE_API_KEY=AIzaSy...Expected result: Environment variables are loaded at build time and your Firebase config uses them instead of hardcoded values.
Use Firebase services in your app
Use Firebase services in your app
Import the exported service instances from your firebase.ts module and use them throughout your application. Each Firebase service has its own modular functions that you import separately. This keeps imports clean and bundle sizes small.
1// Example: Read a document from Firestore2import { db } from "@/lib/firebase";3import { doc, getDoc } from "firebase/firestore";45async function getUser(userId: string) {6 const userDoc = await getDoc(doc(db, "users", userId));7 if (userDoc.exists()) {8 return { id: userDoc.id, ...userDoc.data() };9 }10 return null;11}1213// Example: Check auth state14import { auth } from "@/lib/firebase";15import { onAuthStateChanged } from "firebase/auth";1617onAuthStateChanged(auth, (user) => {18 if (user) {19 console.log("Signed in:", user.uid);20 } else {21 console.log("Not signed in");22 }23});Expected result: Your app can read from Firestore and check auth state using the initialized Firebase services.
Complete working example
1// Firebase initialization module2// Import this file anywhere you need Firebase services3import { initializeApp, getApps } from "firebase/app";4import { getFirestore } from "firebase/firestore";5import { getAuth } from "firebase/auth";6import { getStorage } from "firebase/storage";7import { getAnalytics, isSupported } from "firebase/analytics";89const firebaseConfig = {10 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,11 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,12 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,13 storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,14 messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,15 appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,16};1718// Prevent re-initialization in development (hot reload)19const app =20 getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];2122// Core services23export const db = getFirestore(app);24export const auth = getAuth(app);25export const storage = getStorage(app);2627// Analytics — only in browser, not during SSR28export const analytics = async () => {29 if (typeof window !== "undefined" && (await isSupported())) {30 return getAnalytics(app);31 }32 return null;33};3435export default app;Common mistakes when adding Firebase to an Existing Web Project
Why it's a problem: Calling initializeApp() multiple times due to hot module replacement in development
How to avoid: Check getApps().length before initializing: const app = getApps().length === 0 ? initializeApp(config) : getApps()[0]. This prevents the 'Firebase App already exists' error.
Why it's a problem: Using the compat (v8) import syntax instead of modular v9+ imports
How to avoid: Import from 'firebase/firestore' not 'firebase/compat/firestore'. The compat layer is larger and does not support tree-shaking, resulting in much bigger bundle sizes.
Why it's a problem: Hardcoding Firebase config values directly in source code committed to public repositories
How to avoid: Use environment variables (.env.local) for config values and add .env.local to .gitignore. While the apiKey is safe to expose, keeping config in env vars is a best practice for managing multiple environments.
Why it's a problem: Trying to use getAnalytics() during server-side rendering, causing 'window is not defined' errors
How to avoid: Guard Analytics initialization with typeof window !== 'undefined' and await isSupported() before calling getAnalytics(). Analytics only works in the browser.
Best practices
- Use the modular v9+ import syntax (import { getFirestore } from 'firebase/firestore') for tree-shaking and smaller bundles
- Create a single firebase.ts initialization module and import services from it throughout your app
- Store all Firebase config values in environment variables, even though the apiKey is safe to expose publicly
- Guard against re-initialization with getApps().length check to handle hot module replacement in development
- Initialize Analytics only in the browser with isSupported() to avoid SSR errors in Next.js or Nuxt
- Enable only the Firebase services you actually need to keep dependencies minimal
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Show me how to add Firebase to an existing Next.js TypeScript project using the modular v9+ SDK. I need Firestore, Auth, and Storage initialized in a single module with environment variables for the config values. Include the getApps() guard for hot reload.
Add Firebase to my existing project. Install the firebase npm package, create src/lib/firebase.ts with initializeApp using environment variables, and export getFirestore, getAuth, and getStorage instances. Use modular v9+ imports and guard against re-initialization with getApps().
Frequently asked questions
Is the Firebase apiKey safe to expose in client-side code?
Yes. The apiKey only identifies your Firebase project. It does not grant access to data. Access control is enforced by Firestore Security Rules, Auth rules, and Storage rules. Treat it like a project ID, not a password.
Can I use Firebase with both React and Next.js?
Yes. The Firebase SDK works with any JavaScript framework. The only difference is the environment variable prefix: NEXT_PUBLIC_ for Next.js, VITE_ for Vite/Lovable, and REACT_APP_ for Create React App.
Do I need the Blaze plan to use Firebase in my project?
Not for basic features. Firestore, Auth (email/social), Hosting, and Analytics work on the free Spark plan. You need Blaze for Cloud Functions, phone authentication, and multiple Realtime Database instances.
How do I avoid the 'Firebase App already exists' error?
Check if Firebase is already initialized before calling initializeApp: const app = getApps().length === 0 ? initializeApp(config) : getApps()[0]. This handles hot module replacement in development.
What is the difference between the modular v9+ SDK and the compat SDK?
The modular SDK uses individual function imports (getFirestore, doc, getDoc) that enable tree-shaking, reducing bundle size by up to 80%. The compat SDK uses the older firebase.firestore() syntax and includes all code regardless of what you use.
Can I add Firebase to a project that already uses Supabase?
Yes. Firebase and Supabase can coexist in the same project. A common pattern is using Firebase for Auth and Analytics while using Supabase for the database, or vice versa.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation