· supabase / firebase / migration
Cách migrate Firebase sang Supabase: Hướng dẫn từng bước
Migrate Firebase sang Supabase mà không mất Auth, Firestore hay Storage — thứ tự đúng, 8 điểm chặn cần biết, và khoảng trống Analytics cần lên kế hoạch trước.
Bởi Ethan · Cập nhật 20 tháng 5, 2026
2.348 từ · 12 phút đọc
Bạn có thể migrate Firebase sang Supabase mà không mất auth session, dữ liệu Firestore, hay file trên Storage — nhưng chỉ khi thực hiện đúng thứ tự và biết rõ phần nào không có tương đương. Bài này hướng dẫn migrate Auth, Firestore, và Storage bằng bộ công cụ firebase-to-supabase (153 ⭐, cập nhật 2026-05-15), kèm code so sánh trước/sau cho cả bốn dịch vụ và script Firestore migration đầy đủ.
Một khoảng trống không thể lấp được: Firebase Analytics không có tương đương trong Supabase. Nếu bạn phụ thuộc vào Firebase Analytics, hãy migrate dữ liệu đó sang dịch vụ analytics riêng trước khi động vào bất cứ thứ gì khác.
Bài này dành cho ai
Developer đang dùng Firebase Blaze plan và muốn chuyển sang backend ưu tiên SQL, có thể self-host. Nếu bạn đang dùng Firebase Spark (miễn phí) và chưa gặp giới hạn nào, migration này tốn thời gian thật sự mà lợi ích ngắn hạn chưa rõ — hãy quay lại khi hóa đơn bắt đầu đau.
So sánh dịch vụ Firebase và Supabase
| Dịch vụ Firebase | Tương đương Supabase | Độ phức tạp |
|---|---|---|
| Firebase Auth | Supabase Auth | Trung bình — hash Scrypt được giữ, nhưng định dạng UUID thay đổi |
| Firestore | Postgres | Cao — collection dạng document phải thiết kế lại thành bảng quan hệ |
| Realtime Database | Supabase Realtime | Trung bình — mô hình pub/sub tương tự; giới hạn kết nối khác nhau |
| Cloud Storage | Supabase Storage | Thấp — script download/upload xử lý được |
| Cloud Functions | Edge Functions | Trung bình — khác runtime; hầu hết function chuyển được |
| Firebase Analytics | ❌ Không có tương đương | Lên kế hoạch migrate sang Mixpanel, PostHog, hoặc tương tự |
Nếu bạn vẫn đang cân nhắc có nên migrate không, hãy xem Supabase vs Firebase (2026) để so sánh chi tiết về chi phí và tính năng.
Trước khi migrate: thiết kế schema
Phần khó nhất của quá trình migrate là Firestore, không phải Auth hay Storage. Collection dạng document với cấu trúc lồng tùy ý không ánh xạ gọn gàng vào bảng Postgres. Schema phải được thiết kế trước khi viết bất kỳ migration script nào. Thiết kế sai đồng nghĩa với chạy lại toàn bộ quá trình.
Với mỗi Firestore collection:
- Liệt kê mọi field và kiểu dữ liệu của nó.
- Xác định các object và array lồng nhau. Quyết định: tạo bảng riêng với foreign key, hay dùng cột JSONB?
- Quyết định chiến lược primary key cho Postgres. Document ID của Firebase là string tùy ý. Supabase mặc định dùng UUID. Bạn có thể giữ string ID trong cột
firebase_idđể tra cứu trong giai đoạn chuyển tiếp, nhưng code ứng dụng cuối cùng vẫn phải chuyển sang tra cứu theo UUID.
Ghi lại các quyết định này. Bạn sẽ cần khi chạy firestore2json.js. Khi Firestore đã vào Postgres, bạn cũng sẽ cần một query layer — so sánh TypeScript ORM của chúng tôi đối chiếu Drizzle, Prisma, và Kysely để bạn chọn trước khi viết query đầu tiên.
Migrate Firebase Auth sang Supabase
Quá trình migrate Auth của Supabase giữ nguyên hash mật khẩu Scrypt, nên người dùng hiện tại không cần đặt lại mật khẩu. Hướng dẫn chính thức đi sâu hơn vào từng bước.
Bước 1: Export danh sách user Firebase
firebase auth:export users.json --format=json
Lệnh này ghi toàn bộ bản ghi người dùng — bao gồm các tham số hash Scrypt — vào users.json. Với project có hơn 1 triệu người dùng, thêm --limit và lặp theo từng batch.
Bước 2: Transform và import bằng bộ công cụ
npx github:supabase-community/firebase-to-supabase/auth
Bộ công cụ đọc users.json, ánh xạ Firebase UID sang UUID (duy trì một bảng tra cứu bạn sẽ cần sau cho Firestore), và gọi Supabase Admin API để bulk-insert người dùng. Các tham số hash Scrypt được truyền qua cột raw_app_meta_data, nên việc xác thực vẫn hoạt động với người dùng chưa đăng nhập sau khi migrate.
Lỗi thường gặp: Nếu email của người dùng đã tồn tại trong Supabase (từ lần import một phần trước đó), quá trình import sẽ bỏ qua dòng đó mà không báo lỗi rõ ràng. Kiểm tra số lượng skipped trong log output so với tổng số user Firebase trước khi tiếp tục.
Bước 3: Cấu hình lại Google Sign-In
Google OAuth cần thiết lập lại trong Supabase dashboard tại Authentication → Providers → Google. Client ID và secret từ Firebase project có thể tái sử dụng — chúng gắn với Google Cloud project của bạn, không phải với Firebase.
Migrate dữ liệu Firestore sang Postgres
Bộ công cụ firebase-to-supabase cung cấp một pipeline gồm ba script để migrate Firestore. Cài một lần, chạy theo thứ tự.
git clone https://github.com/supabase-community/firebase-to-supabase
cd firebase-to-supabase
npm install
Giai đoạn 1: Liệt kê các collection
node firestore/collections.js
Kết quả là danh sách tên các collection ở cấp cao nhất. Kiểm tra kỹ — các sub-collection lồng trong document không xuất hiện ở đây và cần xử lý riêng.
Giai đoạn 2: Export Firestore ra JSON
node firestore/firestore2json.js <collection-name> <output-file.json>
Chạy một lần cho mỗi collection. Với collection lớn, thêm --limit và phân trang. Kết quả bao gồm document ID của Firestore dưới dạng trường _id.
Giai đoạn 3: Import JSON vào Postgres
node firestore/json2supabase.js <output-file.json> \
--supabaseUrl=<your-project-url> \
--supabaseKey=<service-role-key> \
--tableName=<target-table>
Công cụ tạo bảng đích nếu chưa có, suy luận kiểu cột từ JSON. Với các object lồng nhau, mặc định là JSONB. Bạn có thể ghi đè bằng cờ --schema trỏ đến file JSON schema.
Script migrate đầy đủ cho collection users:
import { createClient } from '@supabase/supabase-js';
import * as admin from 'firebase-admin';
import * as fs from 'fs';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
const db = admin.firestore();
async function migrateCollection(collectionName: string) {
const snapshot = await db.collection(collectionName).get();
const rows = snapshot.docs.map((doc) => ({
id: crypto.randomUUID(), // UUID mới cho Supabase
firebase_id: doc.id, // giữ lại ID gốc để tra cứu
...doc.data(),
created_at: doc.data().createdAt?.toDate?.() ?? new Date(),
}));
const { error } = await supabase.from(collectionName).insert(rows);
if (error) {
console.error(`Failed on ${collectionName}:`, error.message);
process.exit(1);
}
console.log(`Migrated ${rows.length} records from ${collectionName}`);
}
await migrateCollection('users');
await migrateCollection('posts');
Lỗi thường gặp: Firestore Timestamp được serialize thành object { _seconds, _nanoseconds }, không phải chuỗi ISO. Script trên gọi .toDate() một cách tường minh. Bỏ qua bước này, bạn sẽ nhận được JSONB blob thay vì cột timestamptz như mong muốn.
Migrate Firebase Storage sang Supabase Storage
Migrate Storage là phần ít tốn công nhất. Bộ công cụ cung cấp hai script: một để tải file từ Firebase Storage về máy, một để upload lên Supabase bucket.
Bước 1: Tải file từ Firebase Storage
node storage/download.js \
--firebaseBucket=<your-project>.appspot.com \
--localPath=./storage-backup
Với bucket lớn, quá trình này có thể mất nhiều thời gian. Cấu trúc thư mục local phản ánh cấu trúc thư mục remote.
Bước 2: Upload lên Supabase Storage
node storage/upload.js \
--supabaseUrl=<your-project-url> \
--supabaseKey=<service-role-key> \
--bucketName=<bucket> \
--localPath=./storage-backup
Cài đặt public/private của bucket độc lập với access rule của Firebase — thiết lập trong Supabase dashboard và cập nhật RLS policies tương ứng.
Lỗi thường gặp: Đường dẫn Firebase Storage bắt đầu bằng dấu chấm (.) hoặc chứa dấu gạch chéo liên tiếp (//) có thể gây lỗi khi upload lên Supabase. Kiểm tra trước bằng find ./storage-backup -name ".*" trước khi upload.
Cập nhật client SDK
Sau khi dữ liệu đã migrate xong, cập nhật code ứng dụng. Dưới đây là code so sánh trước/sau cho cả bốn dịch vụ.
Auth: đăng nhập
// Firebase
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth();
const { user } = await signInWithEmailAndPassword(auth, email, password);
// Supabase
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(url, anonKey);
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
const user = data.user;
Database: đọc và ghi
// Firebase — đọc
import { getFirestore, doc, getDoc } from 'firebase/firestore';
const db = getFirestore();
const snap = await getDoc(doc(db, 'posts', postId));
const post = snap.data();
// Supabase — đọc
const { data: post } = await supabase
.from('posts')
.select('*')
.eq('id', postId)
.single();
// Firebase — ghi
import { setDoc } from 'firebase/firestore';
await setDoc(doc(db, 'posts', postId), { title, body });
// Supabase — ghi
await supabase.from('posts').upsert({ id: postId, title, body });
Storage: upload
// Firebase
import { getStorage, ref, uploadBytes } from 'firebase/storage';
const storage = getStorage();
await uploadBytes(ref(storage, `uploads/${filename}`), file);
// Supabase
await supabase.storage
.from('uploads')
.upload(filename, file, { upsert: true });
Realtime: subscribe
// Firebase
import { getDatabase, ref, onValue } from 'firebase/database';
const db = getDatabase();
onValue(ref(db, `rooms/${roomId}`), (snap) => {
console.log(snap.val());
});
// Supabase
supabase
.channel(`room:${roomId}`)
.on('postgres_changes', { event: '*', schema: 'public', table: 'rooms', filter: `id=eq.${roomId}` }, (payload) => {
console.log(payload.new);
})
.subscribe();
Lưu ý sự khác biệt về mô hình: Supabase Realtime subscribe vào các thay đổi row của Postgres, không phải các JSON path tùy ý. Điều này có nghĩa là subscription của bạn luôn gắn với một bảng cụ thể và có thể lọc bằng điều kiện Postgres — có cấu trúc hơn so với Firebase Realtime Database, vốn cho phép subscribe vào bất kỳ path nào.
So sánh giá: Supabase có tiết kiệm hơn không?
| Firebase Blaze | Supabase Pro | |
|---|---|---|
| Giá cơ bản | $0 (pay-as-you-go) | $25/tháng |
| Auth | Miễn phí đến 50K MAU | 100K MAU, sau đó $0.00325/MAU |
| Database | $0.06/GB/tháng (Firestore) | 8 GB, sau đó $0.125/GB |
| Storage | $0.026/GB/tháng | 100 GB, sau đó $0.021/GB |
| Bandwidth | $0.12/GB | 250 GB, sau đó $0.09/GB |
| Kết nối Realtime | Spark: 100; Blaze: 200K/database | 500 đồng thời trên Pro |
Với khoảng 30.000–50.000 monthly active user, Supabase Pro thường rẻ hơn Firebase Blaze về tổng chi phí hàng tháng. Dưới ngưỡng đó, giá pay-as-you-go của Firebase thường có lợi hơn. Trên 500 kết nối Realtime đồng thời, bạn cần plan Team hoặc Enterprise của Supabase.
Supabase Pro còn bao gồm daily backup, branching, và custom domain — những tính năng phải trả thêm hoặc cần giải pháp thay thế trên Firebase.
8 điểm chặn cần biết trước khi bắt đầu
1. Mismatch UUID trong Auth. Firebase UID là string dạng abc123. Supabase UID là UUID. Mọi bảng có foreign key user_id cần được cập nhật sau khi migrate auth. Bộ công cụ tạo bảng mapping firebase_uid_to_supabase_uid để hỗ trợ — dùng nó trước khi xóa các cột firebase_id.
2. Định dạng Timestamp. Firestore Timestamp serialize thành object { _seconds, _nanoseconds } khi export JSON. Gọi .toDate() trước khi insert vào Postgres. Bỏ qua bước này, bạn sẽ insert JSONB blob vào cột timestamptz — Postgres không báo lỗi khi insert, nhưng query sẽ thất bại.
3. Sub-collection lồng nhau. Firestore document có thể chứa sub-collection không xuất hiện trong export cấp cao nhất. Chạy collections.js và tìm sub-collection thủ công. Làm phẳng chúng vào Postgres đòi hỏi thiết kế join table trước khi migrate, không phải sau.
4. Giới hạn kết nối Realtime. Firebase Realtime Database trên Blaze giới hạn 200K kết nối đồng thời mỗi database. Supabase Pro giới hạn 500 kết nối đồng thời. Nếu peak của bạn vượt ngưỡng này, nâng cấp lên plan Team trước khi go live, hoặc triển khai connection pooling phía client.
5. Không có tương đương Firebase Analytics. Đây là khoảng trống khó bù đắp. Lên kế hoạch migrate analytics sang dịch vụ bên thứ ba (Mixpanel, PostHog, Amplitude) như một project riêng. Migrate dữ liệu Firebase Analytics trước; đừng để nó trở thành điểm chặn cho phần còn lại.
6. Google Sign-In cần thiết lập lại. OAuth client ID và secret vẫn dùng được, nhưng bạn phải cấu hình lại Authorized redirect URI trong Google Cloud Console để trỏ đến Auth callback URL của Supabase project. Người dùng Google Sign-In hiện tại sẽ thấy màn hình đồng ý lại nếu redirect URI thay đổi.
7. Không có transaction kiểu Firestore. Firestore hỗ trợ transaction nhiều document trên nhiều collection. Postgres dùng SQL transaction chuẩn, nhưng phương thức rpc() của Supabase client library gọi stored function — dùng stored procedure hoặc function Postgres cho các thao tác atomic nhiều bảng. Viết lại logic transaction thành stored function là phần tốn thời gian nhất khi migrate Firestore phức tạp.
8. RLS tắt theo mặc định. Supabase tạo bảng với Row Level Security tắt. Mọi bảng cần có RLS policy tường minh trước khi bạn có thể dùng anon key phía client. Bỏ sót điều này là rủi ro bảo mật, không phải rủi ro chức năng — ứng dụng vẫn chạy, nhưng mọi row đều có thể đọc công khai. Bật RLS trên mọi bảng ngay sau khi tạo:
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can read own posts"
ON posts FOR SELECT
USING (auth.uid() = user_id);
Thứ tự migrate
- Firebase Analytics → analytics bên ngoài (project song song, bắt đầu trước)
- Firebase Auth → Supabase Auth (user, hash, cấu hình lại OAuth)
- Firestore → Postgres (thiết kế schema, sau đó chạy ba giai đoạn)
- Cloud Storage → Supabase Storage (download → upload)
- Client SDK → thay import, cập nhật tham chiếu foreign key
- RLS policies → bật trước khi chuyển traffic production
Vội vàng làm bước 3–5 trước khi bước 2 hoàn tất khiến các tham chiếu user_id trỏ đến Firebase UID chưa tồn tại trong Supabase. Hãy làm Auth trước.