Performance Optimizations for Auth & Profile Loading โ
Date: January 7, 2026 Issue: Page refresh and login taking significant time, features appearing unavailable during load
Root Causes โ
- Synchronous
getCurrentUser()blocking: The app waited for Firebase'sonAuthStateChangedcallback to resolve before starting to load anything - Sequential profile loading: App tried to load and decrypt the entire profile at once, requiring encryption key availability
- No encryption key caching optimization: Key derivation was done fresh on every login without avoiding duplicate work
- No loading states: Features appeared unavailable while waiting for async operations
Solutions Implemented โ
1. Persistent Auth State Listener (App.jsx) โ
Before:
// Blocks app initialization, waits for promise
async function loadUser() {
const user = await getCurrentUser()
// ... only after this completes
}After:
// Sets up listener immediately, fires when auth state is ready
useEffect(() => {
const unsubscribe = onAuthChange(async (user) => {
setCurrentUser(user)
// Profile loading starts in parallel
})
}, [])Impact: User restored immediately from Firebase's local cache on page refresh, no waiting for promise resolution.
2. Split Public/Encrypted Profile Loading (profileService.js) โ
Before: Single getUserProfile() call that tried to decrypt everything at once, failed if encryption key not available.
After: Three-phase approach:
// Phase 1: Load public data only (FAST - no decryption)
const publicProfile = await getPublicProfile(userId)
setUserProfile(publicProfile)
// Phase 2: Check if encryption key is available
if (hasEncryptionKey()) {
// Phase 3: Decrypt sensitive fields (birthDate) in background
const fullProfile = await decryptProfileFields(publicProfile)
setUserProfile(fullProfile)
}Impact:
- Dashboard renders immediately with public data (lantern name, interests, mood, location preference)
- Encrypted fields (birth date) load as soon as encryption key is available
- No "loading indefinitely" experience
3. Smarter Key Derivation (encryption.js) โ
Before: Each login call to unlockEncryption() independently derived the key, even if multiple requests happened simultaneously.
After:
// If key derivation is already in progress, wait for it instead of duplicating
if (keyDerivationUserId === userId && keyDerivationPromise) {
return keyDerivationPromise
}Impact: Avoids expensive PBKDF2 key derivation duplicates when multiple components request encryption on login.
New exports:
hasEncryptionKey(): Check if key is currently cachedgetCachedUserId(): Get the user ID for cached key
4. Improved Login Flow (LoginFlow.jsx โ App.jsx) โ
After login, the auth state listener automatically:
- Detects new user auth state
- Loads public profile immediately
- Decrypts fields when key is ready
No need to manually reload profile in login handler.
Technical Details โ
Encryption Key Derivation Cost โ
PBKDF2 with 600,000 iterations (OWASP 2023 standard) is intentionally expensive:
- Local machine: ~100-200ms
- Mobile device: ~500-1000ms
- On refresh: Key was being re-derived every time
Optimization: Key is now cached in memory for the session and only re-derived on actual login.
Public vs Encrypted Data โ
Public (fast):
- Email (not shown in UI for privacy)
- Lantern Name
- Interests
- Mood
- Location Tracking preference
- Salt (needed for key derivation)
Encrypted (requires key):
- Birth date
Testing Checklist โ
- [ ] Local dev: Page refresh shows profile instantly
- [ ] Local dev: Features available immediately without "loading" state
- [ ] Dev site (dev.ourlantern.app): Mobile load time significantly reduced
- [ ] Login flow: Profile visible immediately after successful login
- [ ] Login flow: Birth date decrypts once encryption is unlocked
- [ ] Profile settings: Can edit profile without re-login
- [ ] Logout: Encryption key properly cleared
- [ ] Subsequent login: New key derived and cached
Browser DevTools Inspection โ
To verify optimization is working:
Network tab:
/users/{uid}Firestore fetch should be fast (~50-200ms)- Key derivation happens in JS, not network
Console logs (dev mode):
- "๐ Auth state changed" appears immediately on refresh
- "๐ Public profile loaded" appears without "encryption key not cached" warning
- "๐ Birth date decrypted" appears after login
Performance tab:
- Main thread should be responsive within 100-300ms on local
- On mobile/dev site, should see clear improvement vs before
Future Optimizations โ
- Service Worker offline support: Cache public profile in IndexedDB
- Firestore persistence: Already enabled - provides instant access to previously loaded data
- Lazy encryption: Decrypt birth date only when needed (profile view, not dashboard)
- Skeleton screens: Show placeholder UI while public profile loads (optional but improves perceived performance)
Related Files โ
- App.jsx - Auth state listener setup
- profileService.js - Public/encrypted profile loading
- encryption.js - Key caching and derivation
- auth.js - Firebase auth integration