Firebase App Hosting is Firebase's recommended way to deploy a server-side-rendered (SSR) Angular application. It builds your app in Google Cloud Build, runs the Node server, and serves it behind Google's CDN and proxy. This applies to either way of deploying to App Hosting, a connected GitHub repository or firebase deploy from your own machine; both build the same way and are affected the same way.
This guide covers one thing that trips up almost every new SSR deployment: the server can silently stop server-rendering and fall back to client-side rendering (CSR), with no error anywhere obvious. It explains how to detect that in 30 seconds and how to fix it.
If you have not deployed yet, follow Firebase's own guides: Get started with App Hosting to connect a GitHub repository (App Hosting builds and deploys on every push), or Alternative ways to deploy to deploy with firebase deploy from your own machine. Both build your app the same way in Google Cloud Build. The rest of this guide covers an Angular-specific issue you can hit once your app is deployed either way.
A freshly deployed Angular SSR app usually looks fine in a browser, the page renders and works. But the server may be sending an almost-empty HTML shell and letting the browser do all the rendering. When that happens you lose the whole point of SSR: crawlers and link previews see no content, and first paint on slow devices is worse.
There is no error in the deploy output and no error in the browser. The only trace is in your backend's server logs, which nothing prompts you to check.
App Hosting runs on Cloud Run, not Cloud Functions, so its logs live in Cloud Logging:
-
In the Firebase console, open your backend and go to its Logs tab, then look at Runtime logs.
-
From a terminal, use
gcloud:gcloud logging read 'resource.type=cloud_run_revision AND resource.labels.service_name=YOUR_BACKEND_ID' --project YOUR_PROJECT_ID --limit 10
See Firebase's View logs and metrics guide for more.
Pick an SSR route (not an SSG/prerendered one) and fetch it:
curl -s https://YOUR-SITE/ | grep ng-server-contextAngular's server renderer stamps a ng-server-context attribute on the app's root element:
ng-server-context="ssr"- the route was server-rendered (SSR). This is what you want.ng-server-context="ssg"- the route was prerendered (SSG). Fine in itself, but not the SSR path this guide is about.- No
ng-server-contextat all - the server returned a client-only shell and the browser is doing all the rendering. This is the silent CSR fallback described above, regardless of how the page looks in a browser.
Angular's server engine only trusts the X-Forwarded-* headers a proxy attaches to a request when it is told to. App Hosting's proxy adds several of these headers (for example x-forwarded-for and x-forwarded-proto). If Angular does not trust the full set the platform sends, it treats the request as untrusted and de-optimizes to CSR on every request.
Two steps are needed together.
1. Update Angular to the latest patch:
npm update @angular/core @angular/ssr2. Turn on proxy-header trust when you create the server engine. In src/server.ts, pass trustProxyHeaders: true to AngularNodeAppEngine:
import { AngularNodeAppEngine } from '@angular/ssr/node';
const angularApp = new AngularNodeAppEngine({
trustProxyHeaders: true,
});Redeploy, then run the 30-second check above. You should now see ng-server-context="ssr".
The two steps address different halves of the same handshake, and neither alone is enough:
- Recent Angular patches let the
trustProxyHeadersengine option take effect on App Hosting; on older patches a platform environment variable wins instead, so the code option is ignored. Thenpm updategets you onto a patch where the option is honored. - Even on the latest patch, you still have to set the option, so App Hosting's proxy headers are trusted.
This matches Firebase's own guidance. For the current, authoritative version of this fix (including any App Hosting or Angular version notes), see Firebase's App Hosting troubleshooting guide.
Note: App Hosting also has an experimental, opt-in local-build deploy option (you build on your own machine, then
firebase deploy) in which the Firebase CLI applies this proxy-header configuration for you. It is off by default and not a documented, supported workflow, so this guide targets the standard source deploy; apply the fix above.