Skip to content

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

  1. Synchronous getCurrentUser() blocking: The app waited for Firebase's onAuthStateChanged callback to resolve before starting to load anything
  2. Sequential profile loading: App tried to load and decrypt the entire profile at once, requiring encryption key availability
  3. No encryption key caching optimization: Key derivation was done fresh on every login without avoiding duplicate work
  4. No loading states: Features appeared unavailable while waiting for async operations

Solutions Implemented

1. Persistent Auth State Listener (App.jsx)

Before:

javascript
// Blocks app initialization, waits for promise
async function loadUser() {
  const user = await getCurrentUser()
  // ... only after this completes
}

After:

javascript
// 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:

javascript
// 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:

javascript
// 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 cached
  • getCachedUserId(): Get the user ID for cached key

4. Improved Login Flow (LoginFlow.jsx → App.jsx)

After login, the auth state listener automatically:

  1. Detects new user auth state
  2. Loads public profile immediately
  3. 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:

  1. Network tab:

    • /users/{uid} Firestore fetch should be fast (~50-200ms)
    • Key derivation happens in JS, not network
  2. 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
  3. Performance tab:

    • Main thread should be responsive within 100-300ms on local
    • On mobile/dev site, should see clear improvement vs before

Future Optimizations

  1. Service Worker offline support: Cache public profile in IndexedDB
  2. Firestore persistence: Already enabled - provides instant access to previously loaded data
  3. Lazy encryption: Decrypt birth date only when needed (profile view, not dashboard)
  4. Skeleton screens: Show placeholder UI while public profile loads (optional but improves perceived performance)

Built with VitePress