Description
@angular/fire/auth wraps beforeAuthStateChanged as ɵzoneWrap(_beforeAuthStateChanged, true) (src/auth/firebase.ts line 61). With that flag, the wrapper registers a pending task as soon as a callback is passed and clears it only when the callback first fires or the returned unsubscribe runs (src/zones.ts). This callback only fires when a sign-in or sign-out happens, so an app that registers the hook at startup holds the pending task open and ApplicationRef.isStable never becomes true for a visitor who is not signing in or out.
ng build route extraction waits on app stability, so any server-rendered app that registers this hook in its app config cannot build. Extraction hangs for about 30 seconds, then:
AbortError: Routes extraction was aborted.
TimeoutError: The operation was aborted due to timeout
Steps to reproduce
- Fresh
ng new --ssr Angular 21 app, ng add @angular/fire@next.
- In
app.config.ts, register beforeAuthStateChanged inside provideAppInitializer, imported from @angular/fire/auth. Registering it at startup is exactly what an SSR token-cookie sync does (docs/auth.md, Server-side Rendering section).
ng build fails with the error above (34 seconds in my run).
- Change only the import to
firebase/auth. The identical code builds in under 5 seconds.
Expected behavior
Registering the hook does not permanently block app stability. onMessage in src/messaging/firebase.ts wraps the same shape of API, a callback that may never fire, with the flag set to false, which still zone-wraps the callback and only skips the pending task. The same flag here looks like the fix, pending a check that nothing relies on the blocking behavior. onLog in src/app/firebase.ts has the same shape and is worth sweeping at the same time.
Workarounds in the wild
Our own sample already imports beforeAuthStateChanged from firebase/auth (sample/src/app/auth/auth.component.ts), and the rewritten SSR docs section in #3740 documents that import as the way around this bug.
Versions
Reproduced on an Angular 21 app against the @angular/fire 21 canary. The wrapper is unchanged on current main.
Description
@angular/fire/authwrapsbeforeAuthStateChangedasɵzoneWrap(_beforeAuthStateChanged, true)(src/auth/firebase.tsline 61). With that flag, the wrapper registers a pending task as soon as a callback is passed and clears it only when the callback first fires or the returned unsubscribe runs (src/zones.ts). This callback only fires when a sign-in or sign-out happens, so an app that registers the hook at startup holds the pending task open andApplicationRef.isStablenever becomes true for a visitor who is not signing in or out.ng buildroute extraction waits on app stability, so any server-rendered app that registers this hook in its app config cannot build. Extraction hangs for about 30 seconds, then:Steps to reproduce
ng new --ssrAngular 21 app,ng add @angular/fire@next.app.config.ts, registerbeforeAuthStateChangedinsideprovideAppInitializer, imported from@angular/fire/auth. Registering it at startup is exactly what an SSR token-cookie sync does (docs/auth.md, Server-side Rendering section).ng buildfails with the error above (34 seconds in my run).firebase/auth. The identical code builds in under 5 seconds.Expected behavior
Registering the hook does not permanently block app stability.
onMessageinsrc/messaging/firebase.tswraps the same shape of API, a callback that may never fire, with the flag set to false, which still zone-wraps the callback and only skips the pending task. The same flag here looks like the fix, pending a check that nothing relies on the blocking behavior.onLoginsrc/app/firebase.tshas the same shape and is worth sweeping at the same time.Workarounds in the wild
Our own sample already imports
beforeAuthStateChangedfromfirebase/auth(sample/src/app/auth/auth.component.ts), and the rewritten SSR docs section in #3740 documents that import as the way around this bug.Versions
Reproduced on an Angular 21 app against the @angular/fire 21 canary. The wrapper is unchanged on current
main.