Why Developers Are Switching from Firebase to Supabase (And You Should Too)
Something shifted in the developer world.
Firebase used to be the default answer to "what backend should I use?" For years, it was the obvious choice — fast setup, real-time database, free tier, backed by Google. Done.
But scroll through any developer community in 2026 — Reddit, X, Discord, Hacker News — and you will see the same pattern: developers are leaving Firebase for Supabase. Not just a few. Thousands.
This is not hype. There are real, practical reasons driving this migration. And if you are still on Firebase, this article will show you exactly why the switch is happening — and how to do it yourself.
The 4 Reasons Developers Are Leaving Firebase
1. The Pricing Time Bomb
This is the number one reason. Every Firebase developer has a horror story.
Firebase charges per document read and write. Sounds fine until your app gets traffic:
Small app (1,000 users):
├── 500K Firestore reads/month → $0.30
├── 100K Firestore writes/month → $0.18
└── Total: ~$0.50/month → Feels free!
Growing app (50,000 users):
├── 50M Firestore reads/month → $30
├── 10M Firestore writes/month → $18
├── Cloud Functions → $15
└── Total: ~$63/month → Getting expensive
Popular app (500,000 users):
├── 500M Firestore reads/month → $300
├── 100M Firestore writes/month → $180
├── Cloud Functions → $80
├── Bandwidth → $50
└── Total: ~$610/month → WHERE DID THIS COME FROM?
The problem is not just the cost — it's the unpredictability. A single viral moment, a bot scraping your app, or a poorly optimized query can spike your bill overnight.
Real story: A developer shipped a dashboard that fetched 100 Firestore documents on page load. With 10,000 daily active users, that is 1 million reads per day — 30 million per month — just from one page. The monthly bill jumped from $15 to $180 overnight.
Supabase pricing is flat. $25/month for Pro. Your app can make a billion queries — the price stays the same. You pay for compute and storage, not for how many times your users press a button.
2. NoSQL Hit a Wall
Firestore seemed great when your app was simple. But then you needed to:
- Show a leaderboard → No ORDER BY with LIMIT across collections
- Count total users → No COUNT — you read every document or maintain a counter
- Join user data with orders → No JOINs — you denormalize or make multiple queries
- Search by multiple fields → Requires composite indexes for every combination
- Run analytics → Read every document, aggregate client-side
Here is what a simple "top customers" query looks like in both:
Supabase (PostgreSQL):
SELECT
users.name,
COUNT(orders.id) as order_count,
SUM(orders.total) as total_spent
FROM users
JOIN orders ON users.id = orders.user_id
WHERE orders.created_at > NOW() - INTERVAL '30 days'
GROUP BY users.id
ORDER BY total_spent DESC
LIMIT 10;
One query. One database call. Instant.
Firebase (Firestore):
// Step 1: Get all orders from last 30 days
const ordersSnap = await getDocs(
query(
collection(db, 'orders'),
where('createdAt', '>', thirtyDaysAgo)
)
);
// Step 2: Group by userId (client-side)
const userTotals = {};
ordersSnap.forEach(doc => {
const order = doc.data();
if (!userTotals[order.userId]) {
userTotals[order.userId] = { count: 0, total: 0 };
}
userTotals[order.userId].count++;
userTotals[order.userId].total += order.total;
});
// Step 3: Sort client-side
const topCustomers = Object.entries(userTotals)
.sort(([, a], [, b]) => b.total - a.total)
.slice(0, 10);
// Step 4: Fetch each user's name (N more reads!)
const results = await Promise.all(
topCustomers.map(async ([userId, stats]) => {
const userDoc = await getDoc(doc(db, 'users', userId));
return { name: userDoc.data().name, ...stats };
})
);
That is potentially thousands of document reads for something PostgreSQL does in one query. And you are paying for every single read.
3. Vendor Lock-In Is Real
With Firebase:
- Your data is in Firestore (proprietary NoSQL format)
- Your auth is in Firebase Auth (Google-only)
- Your functions are Cloud Functions (Google Cloud)
- You cannot self-host any of it
- Exporting data means manually converting documents to another format
If Google changes pricing, deprecates a feature, or you just want to move — you are rewriting your entire backend.
With Supabase:
- Your data is in PostgreSQL — the industry standard
- Export with
pg_dump, import anywhere - Fully open-source — self-host with Docker
- Zero proprietary formats
# Leave Supabase? Take your data with you.
pg_dump -h db.yourproject.supabase.co -U postgres -d postgres > backup.sql
# Restore to any PostgreSQL host
psql -h your-new-host.com -U postgres -d myapp < backup.sql
Supabase is the only major BaaS that is fully open-source. If Supabase disappeared tomorrow, you could self-host everything with Docker and keep building. Try doing that with Firebase.
4. Server-Side Rendering Changed Everything
Firebase was built for a world of client-side SPAs — single-page applications where JavaScript runs in the browser.
But the web moved on. Next.js App Router, Server Components, Server Actions — the future is server-first. And Firebase does not fit.
The Firebase problem with Next.js:
// You need TWO different Firebase SDKs
import { getFirestore } from 'firebase/firestore' // Client
import { getFirestore } from 'firebase-admin/firestore' // Server
// Different APIs, different auth contexts, different behavior
// Client SDK uses user tokens, Admin SDK bypasses security rules
// You constantly context-switch between two mental models
Supabase with Next.js:
// One API, works everywhere
import { createClient } from '@/lib/supabase/server'
// Server Component
export default async function Page() {
const supabase = await createClient()
const { data } = await supabase.from('posts').select('*')
return <PostList posts={data} />
}
// Server Action
export async function createPost(formData: FormData) {
const supabase = await createClient()
await supabase.from('posts').insert({ title: formData.get('title') })
revalidatePath('/posts')
}
Same client, same API, same mental model. Supabase's @supabase/ssr package was built for the server-first world.
How to Migrate: Step-by-Step
Ready to switch? Here is the practical migration path.
Step 1: Map Your Firestore Collections to PostgreSQL Tables
Firestore documents become rows. Collections become tables. Nested data gets normalized.
Before (Firestore):
users/
userId1/
name: "Alice"
email: "alice@example.com"
orders/
orderId1/
product: "Widget"
amount: 29.99
status: "shipped"
After (PostgreSQL):
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
product TEXT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status TEXT DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see own data" ON users
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users see own orders" ON orders
FOR SELECT USING (auth.uid() = user_id);
Step 2: Migrate Auth Users
Export from Firebase, import to Supabase:
// scripts/migrate-auth.ts
import admin from 'firebase-admin'
import { createClient } from '@supabase/supabase-js'
const firebase = admin.initializeApp({
credential: admin.credential.cert('./firebase-service-account.json')
})
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
async function migrateUsers() {
const listResult = await firebase.auth().listUsers(1000)
for (const user of listResult.users) {
const { data, error } = await supabase.auth.admin.createUser({
email: user.email!,
email_confirm: true,
user_metadata: {
name: user.displayName,
avatar_url: user.photoURL,
firebase_uid: user.uid // keep reference
}
})
if (error) {
console.error(`Failed to migrate ${user.email}:`, error.message)
} else {
console.log(`Migrated: ${user.email} → ${data.user.id}`)
}
}
}
migrateUsers()
Send your users an email before migration: "We are upgrading our infrastructure. You may need to reset your password on your next login." This covers the password hash issue gracefully.
Step 3: Migrate Data
Export Firestore collections to PostgreSQL:
// scripts/migrate-data.ts
import admin from 'firebase-admin'
import { createClient } from '@supabase/supabase-js'
const db = admin.firestore()
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
async function migrateCollection(
collectionName: string,
tableName: string,
transform: (doc: any) => any
) {
const snapshot = await db.collection(collectionName).get()
const rows = snapshot.docs.map(doc => transform({
firebase_id: doc.id,
...doc.data()
}))
// Insert in batches of 500
for (let i = 0; i < rows.length; i += 500) {
const batch = rows.slice(i, i + 500)
const { error } = await supabase.from(tableName).insert(batch)
if (error) {
console.error(`Batch ${i} failed:`, error.message)
} else {
console.log(`Migrated ${Math.min(i + 500, rows.length)}/${rows.length} ${collectionName}`)
}
}
}
// Migrate users first (referenced by other tables)
await migrateCollection('users', 'users', (doc) => ({
id: doc.firebase_id, // or map to new Supabase auth user ID
name: doc.name,
email: doc.email,
created_at: doc.createdAt?.toDate() || new Date()
}))
// Then migrate orders
await migrateCollection('orders', 'orders', (doc) => ({
user_id: doc.userId, // map to new user ID
product: doc.product,
amount: doc.amount,
status: doc.status,
created_at: doc.createdAt?.toDate() || new Date()
}))
Step 4: Update Your App Code
Replace Firebase SDK calls with Supabase equivalents:
// BEFORE: Firebase
import { collection, getDocs, addDoc, query, where } from 'firebase/firestore'
const q = query(collection(db, 'tasks'), where('userId', '==', user.uid))
const snapshot = await getDocs(q)
const tasks = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
await addDoc(collection(db, 'tasks'), {
title: 'New task',
userId: user.uid,
completed: false
})
// AFTER: Supabase
import { createClient } from '@/lib/supabase/server'
const supabase = await createClient()
// RLS handles the user filtering automatically
const { data: tasks } = await supabase
.from('tasks')
.select('*')
.order('created_at', { ascending: false })
await supabase.from('tasks').insert({
title: 'New task',
// user_id is set automatically via RLS + auth.uid()
})
Notice how the Supabase version is shorter? You do not need to filter by user ID — Row Level Security handles it automatically. Less code, fewer bugs.
Step 5: Replace Firebase Services
| Firebase Service | Supabase Equivalent |
|---|---|
| Firestore | PostgreSQL database |
| Firebase Auth | Supabase Auth |
| Cloud Storage | Supabase Storage |
| Cloud Functions | Supabase Edge Functions + Next.js Server Actions |
| Hosting | Vercel (for Next.js) |
| Realtime Database | Supabase Realtime |
| Analytics | PostHog or Plausible |
| Crashlytics | Sentry |
| Cloud Messaging | OneSignal or Novu |
Migration Timeline
For a typical mid-size app:
Week 1:
├── Day 1-2: Set up Supabase project, design PostgreSQL schema
├── Day 3-4: Migrate auth users
└── Day 5: Migrate data
Week 2:
├── Day 1-3: Update app code (replace Firebase SDK with Supabase)
├── Day 4: Test everything
└── Day 5: Deploy + monitor
You do not have to migrate everything at once. Start with new features on Supabase. Migrate existing features one at a time. Run both in parallel until you are confident.
The Results After Switching
Developers who migrated report:
- 60-80% lower monthly costs — flat pricing vs per-read billing
- Faster development — SQL queries replace complex NoSQL workarounds
- Better Next.js integration — first-party SSR support, Server Actions
- Simpler codebase — one API everywhere, RLS handles authorization
- Peace of mind — open source, no vendor lock-in, data portability
Should You Switch?
Switch now if:
- Your Firebase bill is growing faster than your revenue
- You are fighting Firestore's query limitations
- You are building with Next.js (App Router)
- You want data ownership and portability
- You are starting a new project
Stay on Firebase if:
- Your app is deeply integrated with Google Cloud services
- You rely heavily on Firebase Cloud Messaging for push notifications
- Your team has deep Firebase expertise and no SQL experience
- Your app is small, stable, and Firebase costs are negligible
For everyone else — especially if you are building with Next.js — the switch to Supabase is one of the best decisions you will make in 2026.
Ready to start building? Check out our step-by-step tutorial to build a full-stack app with Next.js and Supabase — you will have a working app in 20 minutes.
Related:
- Build a Full-Stack App with Next.js and Supabase in 20 Minutes
- Supabase vs Firebase in 2026: The Honest Comparison
- Building SaaS with Next.js and Supabase
- Next.js + Supabase Migration Strategies
- Supabase RLS Policy Design Patterns
Originally published at https://www.iloveblogs.blog
Top comments (0)