Deploy a web app with Firebase in 7 steps—auth, Firestore, hosting, functions. Real configs, security rules, and cost traps for solo founders.
Firebase takes you from localhost to production in hours, not weeks. Authentication, database, hosting, and deployment are all in one platform — no server provisioning, no Docker, no infrastructure rabbit holes. For solo founders who need to ship, Firebase lifts the ops burden so you can focus on product.
Photo: Lee Campbell on Unsplash
Target audience: Indie hackers and solo developers building web apps, desiring a backend without server management. If you're good with JavaScript and need auth, real-time data, and hosting all together, this is your stack.
Step 1: Set Up Firebase Project and Install CLI
First, head to the Firebase Console and create a new project. Firebase will auto-generate a project ID needed for deployment.
Install the Firebase CLI globally:
npm install -g firebase-tools
Log in:
firebase login
This opens a browser OAuth flow. Once authenticated, the CLI keeps credentials locally. No environment variables, no manual token management needed.
Initialize Firebase in your project directory:
firebase init
Select Firestore, Authentication, Hosting, and Functions if serverless endpoints are required. The CLI will generate firebase.json and .firebaserc config files, defining deployment rules and resource allocation.
Key config often missed: The firebase.json hosting section lets you set custom headers, rewrites, and redirects. Most tutorials skip this. Building an SPA? Include this rewrite rule to serve index.html for all routes:
{
"hosting": {
"public": "dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
Without it, direct URL navigation returns 404s, which trips up client-side routing.
Step 2: Configure Firestore Database
Photo: Christopher Gower on Unsplash
Firestore is Firebase's NoSQL document database. It's real-time, scalable, and requires no server management. Data is modeled as collections and documents.
In the Firebase Console, navigate to Firestore Database > Create Database. Choose between production mode or test mode. Test mode allows open read/write for 30 days — useful for prototyping but risky for production.
Structure your data wisely. Firestore charges based on reads, writes, and deletes. Poor schema design leads to unnecessary queries, resulting in higher costs.
Example schema for a task app:
/users/{userId}
- email: string
- createdAt: timestamp
/tasks/{taskId}
- userId: string
- title: string
- completed: boolean
- createdAt: timestamp
Install the Firestore SDK in your web app:
npm install firebase
Initialize Firestore in your app:
import { initializeApp } from 'firebase/app';
import { getFirestore, collection, addDoc } from 'firebase/firestore';
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID"
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
// Add a document
async function addTask(title) {
const docRef = await addDoc(collection(db, 'tasks'), {
title: title,
completed: false,
createdAt: new Date()
});
console.log('Task added with ID:', docRef.id);
}
Critical mistake: Developers often query Firestore without indexes. If filtering or sorting on multiple fields, Firestore needs a composite index. Only in production do you realize this. Plan queries in advance and create indexes early.
According to Firebase's official documentation, composite indexes must be manually configured for complex queries. Auto-indexing only covers single-field queries.
Step 3: Add Firebase Authentication
Firebase Authentication manages sign-up, login, password reset, and social auth (Google, GitHub, Twitter) with minimal code.
Enable authentication providers in Firebase Console > Authentication > Sign-in method. For most solo projects, email/password and Google sign-in cover 90% of use cases.
Implement email/password authentication:
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth();
// Sign up
async function signUp(email, password) {
try {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
console.log('User created:', userCredential.user.uid);
} catch (error) {
console.error('Error:', error.message);
}
}
// Sign in
async function signIn(email, password) {
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
console.log('User signed in:', userCredential.user.uid);
} catch (error) {
console.error('Error:', error.message);
}
}
Add Google sign-in:
import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth';
const provider = new GoogleAuthProvider();
async function signInWithGoogle() {
try {
const result = await signInWithPopup(auth, provider);
console.log('User signed in with Google:', result.user.email);
} catch (error) {
console.error('Error:', error.message);
}
}
What nobody tells you: Firebase Auth tokens expire after one hour. The SDK auto-refreshes tokens. However, if passing tokens to your backend or third-party APIs, manually refresh them:
import { getAuth } from 'firebase/auth';
const auth = getAuth();
const user = auth.currentUser;
if (user) {
const token = await user.getIdToken(true); // Force refresh
// Send token to your backend
}
Ignoring token expiry leads to intermittent 401 errors that are tough to debug.
Step 4: Write Firestore Security Rules
Firestore Security Rules control who can read or write data. Default test mode allows all access — fine for prototyping but catastrophic in production.
Open Firestore > Rules in Firebase Console. Use Firestore's rule language to write rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only read/write their own user document
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Users can only read/write their own tasks
match /tasks/{taskId} {
allow read, write: if request.auth != null &&
request.auth.uid == resource.data.userId;
allow create: if request.auth != null &&
request.auth.uid == request.resource.data.userId;
}
}
}
These rules ensure users can only access their own data. Without them, any authenticated user can read or modify any document.
Common mistake: Developers often write overly permissive rules ("I'll tighten them later") and forget. Data leaks happen. Write restrictive rules from day one.
Test rules in the Firebase Console's Rules Playground. Simulate authenticated requests with different user IDs to verify access control before deployment.
Step 5: Deploy Cloud Functions for Backend Logic
Firebase Cloud Functions are serverless functions triggered by HTTP requests, Firestore changes, or scheduled events. They run Node.js in Google's infrastructure.
Initialize Functions:
firebase init functions
This creates a functions/ directory with index.js. Here's a simple HTTP endpoint:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createTask = functions.https.onCall(async (data, context) => {
// Verify user is authenticated
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated', 'User must be authenticated');
}
const { title } = data;
const userId = context.auth.uid;
// Add task to Firestore
const taskRef = await admin.firestore().collection('tasks').add({
userId: userId,
title: title,
completed: false,
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
return { taskId: taskRef.id };
});
Call this function from your frontend:
import { getFunctions, httpsCallable } from 'firebase/functions';
const functions = getFunctions();
const createTask = httpsCallable(functions, 'createTask');
async function addNewTask(title) {
try {
const result = await createTask({ title: title });
console.log('Task created:', result.data.taskId);
} catch (error) {
console.error('Error:', error.message);
}
}
Deploy functions:
firebase deploy --only functions
Performance trap: Cold starts on Cloud Functions can add 1-3 seconds of latency. According to Google Cloud's documentation, minimizing dependencies and choosing lightweight runtimes help reduce cold start times. For latency-sensitive operations, keep functions small and consider Firebase's min instances setting (a paid feature) to keep functions warm.
Step 6: Configure Hosting and Deploy Frontend
Firebase Hosting serves static assets with a global CDN. It integrates with popular frameworks (React, Vue, Next.js, Vite) and supports custom domains, SSL, and automatic HTTP/2.
Build your frontend for production:
npm run build
This typically outputs to a dist/ or build/ directory. Update firebase.json to point to your build directory:
{
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}
Deploy:
firebase deploy --only hosting
Firebase provides a temporary URL: your-project-id.web.app. To add a custom domain, go to Hosting > Add custom domain in Firebase Console. Firebase auto-provisions SSL certificates via Let's Encrypt.
Caching gotcha: Firebase Hosting aggressively caches static assets. If you update your app and users see old versions, it's because of cache headers. Set cache control in firebase.json:
{
"hosting": {
"public": "dist",
"headers": [
{
"source": "**/*.@(jpg|jpeg|gif|png|svg|webp)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=31536000"
}
]
},
{
"source": "**/*.@(js|css)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=31536000"
}
]
},
{
"source": "index.html",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, no-store, must-revalidate"
}
]
}
]
}
}
Cache static assets for a year, but force index.html to revalidate. This ensures users get updates immediately while benefiting from CDN caching for assets.
Step 7: Monitor Usage and Set Billing Alerts
Firebase offers a generous free tier (Spark plan): 50K reads/day, 20K writes/day, 1GB storage, 10GB hosting transfer. For prototyping, this is usually sufficient. In production, expect to exceed these limits.
Upgrade to the Blaze plan (pay-as-you-go). Set billing alerts to avoid surprise charges. Go to Firebase Console > Project Settings > Usage and Billing > Set budget alerts.
Monitor key metrics in Firebase Console:
- Firestore usage: Reads, writes, deletes per day
- Functions invocations: Total calls, execution time, memory usage
- Hosting bandwidth: Data transfer in GB
Firebase's pricing scales linearly, but inefficient queries can spike costs. A poorly optimized query that scans thousands of documents can quickly deplete your read quota.
Real-world cost illustration: A solo founder reported on Indie Hackers that a runaway Firestore listener (left active after component unmount in React) generated 2.3 million reads in a week, costing $140. Always clean up listeners:
import { onSnapshot } from 'firebase/firestore';
const unsubscribe = onSnapshot(collection(db, 'tasks'), (snapshot) => {
// Handle updates
});
// Clean up when component unmounts
useEffect(() => {
return () => unsubscribe();
}, []);
Common Mistakes Solo Founders Make with Firebase
1. Ignoring security rules until production. Firebase's test mode expires after 30 days. If you launch publicly without writing rules, your database is world-readable. Apps have been seen on Product Hunt with open Firestore instances — all user data exposed.
2. Using Firestore like SQL. Firestore is optimized for document retrieval, not complex joins or aggregations. If relational queries are needed, consider PostgreSQL on Supabase or PlanetScale. Firebase excels for user-scoped data (tasks, notes, settings) where queries are by user ID.
3. Over-relying on client-side security. Security rules enforce access control, but they're not input validation. Malformed data can still be written by users. Use Cloud Functions for server-side validation, especially for financial or sensitive operations.
4. Not pre-warming Cloud Functions. On the Blaze plan, set minInstances: 1 for critical functions to avoid cold starts. This costs ~$5/month per function but eliminates 2-second delays for the first user each hour.
5. Skipping local emulators. Firebase provides local emulators for Firestore, Auth, and Functions. Developing against production is slow and risky. Install emulators:
firebase init emulators
Run emulators:
firebase emulators:start
This spins up local instances on localhost:8080 (Firestore), localhost:9099 (Auth), and localhost:5001 (Functions). Zero latency, zero cost, zero risk.
FAQ
How much does Firebase cost for a typical web app?
Firebase's free tier covers most prototypes and low-traffic apps. Once you exceed 50K Firestore reads/day or 10GB hosting bandwidth/month, you pay per usage. Typical costs for a small SaaS with 500 active users: $20-$50/month for Firestore, $5-$15/month for Functions, $0-$5/month for hosting. Use Firebase's pricing calculator to estimate based on expected usage.
Can Firebase be used with React, Vue, or Next.js?
Yes. Firebase is framework-agnostic, meaning it works with any JavaScript framework. For React, install firebase via npm and initialize in a context provider. For Next.js, use Firebase on both client and server (API routes). For server-side rendering, fetch data in getServerSideProps or getStaticProps using the Firebase Admin SDK.
How do you migrate from Firebase to another platform later?
Export Firestore data via the Firebase Console or CLI (gcloud firestore export). Auth user data can be exported as JSON. Hosting and Functions are standard web tech — static files and Node.js — so migration is straightforward. The lock-in risk is Firestore's NoSQL structure. Want to switch to PostgreSQL later? You'll need to rewrite queries and schema, but the data export itself is simple.
Is Firebase suitable for real-time features like chat or collaboration?
Firestore's real-time listeners work well for real-time features with moderate concurrency (hundreds of users). For high-concurrency apps (thousands of simultaneous users), Firestore's pricing model (per document read on every update) becomes expensive. Consider Firebase Realtime Database (older but cheaper for real-time) or third-party services like Ably or Pusher for extreme concurrency.
Next Step: Ship Your First Feature
Choose one feature — user sign-up or a single Firestore collection — and implement it end-to-end using these steps. Deploy to Firebase Hosting today. Avoid architecting the entire app upfront. Firebase's real strength is in rapid iteration. Build, deploy, test, repeat. The platform removes infrastructure headaches so the focus can be on product-market fit, not DevOps. If you're interested in building more complex applications, consider checking out how to Launch a SaaS in 30 Days with Bubble: Real Steps or explore the differences between tools in Webflow vs. Elementor: Which Tool Ships Faster?.
Editorial note: This article was produced with AI assistance and reviewed by Javier Valencia. Verified facts are distinguished from editorial opinion throughout the text. External sources linked are independent of NewsTide.
Sources
🇪🇸 Also available in Spanish: Leer en español