Firebase v11+: Environment-Safe Modular Setup
Tutor's Note: Instead of hardcoding Firebase credentials, we leverage environment variables. This allows secure separation between Dev, Staging, and Prod environments, while keeping your firebase.js modular and tree-shakable.
Firebase v11+ Modular Setup With Environment Variables
Using environment variables ensures your credentials are never exposed in source code, and makes swapping between multiple environments effortless. This approach is perfect for SvelteKit and modern bundlers like Vite.
1. Installing Firebase
Install Firebase in your project:
npm install firebase
2. Setting Up Your Environment Variables
Create a .env file for each environment (dev, prod) and store your Firebase configuration there. Example:
PUBLIC_FIREBASE_API_KEY=your-api-key
PUBLIC_FIREBASE_AUTH_DOMAIN=your-app-dev.firebaseapp.com
PUBLIC_FIREBASE_PROJECT_ID=your-app-dev
PUBLIC_FIREBASE_STORAGE_BUCKET=your-app-dev.appspot.com
PUBLIC_FIREBASE_MESSAGING_SENDER_ID=1234567890
PUBLIC_FIREBASE_APP_ID=1:1234567890:web:abcdef123456
PUBLIC_RECAPTCHA_SITE_KEY=your-recaptcha-key
3. The Modular firebase.js
Use this environment-safe Firebase setup:
import { initializeApp, getApps } from 'firebase/app';
import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';
import { getFirestore } from 'firebase/firestore';
import { browser } from '$app/environment';
import {
PUBLIC_FIREBASE_API_KEY,
PUBLIC_FIREBASE_AUTH_DOMAIN,
PUBLIC_FIREBASE_PROJECT_ID,
PUBLIC_FIREBASE_STORAGE_BUCKET,
PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
PUBLIC_FIREBASE_APP_ID,
PUBLIC_RECAPTCHA_SITE_KEY
} from '$env/static/public';
const firebaseConfig = {
apiKey: PUBLIC_FIREBASE_API_KEY,
authDomain: PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: PUBLIC_FIREBASE_APP_ID
};
// Initialize Firebase app
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
// Export Firestore
export const db = getFirestore(app);
// Initialize App Check (client-only)
export let appCheck;
if (browser) {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}
appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider(PUBLIC_RECAPTCHA_SITE_KEY),
isTokenAutoRefreshEnabled: true
});
}
4. Benefits of This Approach
- Security: Credentials never live in your repo.
- Environment Flexibility: Easily switch between dev, staging, and prod.
- Modular: Only imports Firebase features you need.
- Tree-shakable: Smaller bundle size for modern front-end apps.
5. Using Firestore
After importing db, your real-time updates are modular:
import { collection, onSnapshot, query, orderBy } from 'firebase/firestore';
import { db } from './firebase.js';
const postsRef = collection(db, 'posts');
const q = query(postsRef, orderBy('createdAt', 'desc'));
onSnapshot(q, snapshot => {
const posts = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
renderBlogUI(posts);
});
6. Uploading Files Securely
Environment-safe Firebase Storage usage:
import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { app } from './firebase.js';
const storage = getStorage(app);
async function uploadFile(file) {
const storageRef = ref(storage, `uploads/${file.name}`);
await uploadBytes(storageRef, file);
return await getDownloadURL(storageRef);
}
7. Deploying with firebase.json
Your firebase.json remains environment-agnostic. Keys are never exposed, and you can still define CSP headers, rewrites, and redirects as usual.
Conclusion
By following this environment-variable approach, you make your Firebase integration secure, modular, and production-ready — all while keeping things easy for new developers to experiment locally.