I'm experiencing a critical issue with my NextJS/Firebase application where approximately 40% of random user registrations result in "orphaned" users - users that exist in Firebase Authentication but have no corresponding document in Firestore.
The issue occurs randomly with no consistent pattern or error messages:
- Users are 100% successfully created in Firebase Authentication
- But approximately 40% of random registration attempts fail to create the Firestore document
- No errors are thrown during this process
- Affected users see a "User not approved" error on our Login form
- These users can't get their pending registrations approved from the admin side because our app checks for Firestore documents
Here's my user registration function in AuthContext.js:
// New user registration
const register = async (email, password, userData) => {
setErrorMessage('');
let user = null;
try {
setLoading(true);
// 1. Create user in Firebase Auth
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
user = userCredential.user;
// 2. Create user document data
const userDocData = {
email: email,
name: userData.name || '',
surname: userData.surname || '',
phone: userData.phone || '',
role: userData.role || 'student',
status: 'pending',
createdAt: serverTimestamp(),
updatedAt: serverTimestamp(),
package: userData.role === 'student' ? {
remainingClasses: 0,
paymentStatus: false
} : null
};
// 3. Create document in Firestore
const batch = writeBatch(db);
batch.set(doc(db, 'users', user.uid), userDocData);
await batch.commit();
// 4. Verify document creation
const checkDoc = await getDoc(doc(db, 'users', user.uid));
if (!checkDoc.exists()) {
throw new Error('User document was not created');
}
// 5. Logout after successful registration
await signOut(auth);
return user;
} catch (error) {
// If Auth user created but Firestore document failed, delete Auth user
if (user) {
try {
// Try to delete any partially created document
try {
await deleteDoc(doc(db, 'users', user.uid));
} catch (docError) {
console.error("Error deleting document:", docError);
}
// Delete Auth user
await deleteUser(user);
} catch (deleteError) {
console.error('Error deleting user after failed registration:', deleteError);
}
}
// Handle specific errors
switch (error.code) {
case 'auth/email-already-in-use':
setErrorMessage('Αυτή η διεύθυνση email χρησιμοποιείται ήδη.');
break;
case 'auth/invalid-email':
setErrorMessage('Μη έγκυρη διεύθυνση email.');
break;
case 'auth/weak-password':
setErrorMessage('Ο κωδικός πρέπει να έχει τουλάχιστον 6 χαρακτήρες.');
break;
default:
setErrorMessage('Σφάλμα κατά την εγγραφή. Παρακαλώ δοκιμάστε ξανά.');
break;
}
throw error;
} finally {
setLoading(false);
}
};
I've also tried implementing a retry mechanism with exponential backoff:
// 3. Create document with retry mechanism
let success = false;
let attempts = 0;
while (!success && attempts < 5) {
try {
attempts++;
console.log(`Attempt ${attempts} to create Firestore document`);
const batch = writeBatch(db);
batch.set(doc(db, 'users', user.uid), userDocData);
await batch.commit();
// Immediate verification
const checkDoc = await getDoc(doc(db, 'users', user.uid));
if (checkDoc.exists()) {
success = true;
console.log(`Successfully created Firestore document on attempt ${attempts}`);
} else {
console.warn(`Document check failed after batch commit (attempt ${attempts})`);
// Wait before retrying (exponential backoff)
await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
}
} catch (error) {
console.error(`Firestore error on attempt ${attempts}:`, error);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
}
}
if (!success) {
throw new Error(`Failed to create Firestore document after ${attempts} attempts`);
}
I've also implemented a temporary workaround in onAuthStateChanged:
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (user) => {
if (user) {
try {
// Check if document exists
const userDoc = await getDoc(doc(db, 'users', user.uid));
// If document doesn't exist, create it (fix orphaned users)
if (!userDoc.exists()) {
console.log("Orphaned user detected. Synchronizing...");
await setDoc(doc(db, 'users', user.uid), {
email: user.email,
name: '',
surname: '',
phone: '',
role: 'student',
status: 'pending',
createdAt: serverTimestamp(),
updatedAt: serverTimestamp(),
package: {
remainingClasses: 0,
paymentStatus: false
}
});
// Continue with normal checks...
}
} catch (error) {
console.error('Error fetching user data:', error);
setCurrentUser(null);
}
} else {
setCurrentUser(null);
}
setLoading(false);
});
return () => unsubscribe();
}, [auth, db, router]);
What I've Tried
- Using
writeBatchinstead ofsetDoc- same issue occurs - Adding a retry mechanism with up to 5 attempts - still fails occasionally
- Adding verification after document creation - confirms document isn't being created
- Adding proper cleanup to delete orphaned Authentication users
- Adding detailed logging - no errors appear in logs when the issue occurs
Environment
- NextJS 14
- Firebase v9
- Using the client SDK
- Using the initialization pattern with singleton instance
What could be causing this inconsistent behavior where Firebase Auth users are created but Firestore documents fail silently ~40% of the time? I need a reliable solution as this affects a significant portion of our user registrations.
Is there a way to ensure atomicity between Auth and Firestore operations? Would using Firebase Cloud Functions for user creation provide better reliability?