Skip to content

Commit 6388192

Browse files
docs: add App Hosting SSR deployment guide
Adds docs/app-hosting.md documenting the silent SSR-to-CSR fallback that trips up Angular apps deployed to Firebase App Hosting: how to detect it with the ng-server-context marker, and the two-step fix (update @angular/core and @angular/ssr, then set trustProxyHeaders: true on AngularNodeAppEngine). It applies to both public deploy paths, a connected GitHub repo and firebase deploy, which build the same way, and defers to Firebase's App Hosting troubleshooting guide as the authoritative source. Links the guide from the README Resources section.
1 parent 96cf855 commit 6388192

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export class AppComponent {
6666

6767
[Quickstart](docs/install-and-setup.md) - Get your first application up and running by following our quickstart guide.
6868

69+
[Deploying SSR to App Hosting](docs/app-hosting.md) - Deploy a server-rendered app to Firebase App Hosting, and avoid the silent SSR-to-CSR fallback.
70+
6971
[Contributing](CONTRIBUTING.md)
7072

7173
[Stackblitz Template](https://stackblitz.com/edit/angular-fire-start) - Remember to set your Firebase configuration in `app/app.module.ts`.

‎docs/app-hosting.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<small>
2+
<a href="https://github.com/angular/angularfire">AngularFire</a> &#10097; <a href="../README.md#developer-guide">Developer Guide</a> &#10097; Deploying SSR to Firebase App Hosting
3+
</small>
4+
5+
# Deploying a server-rendered app to Firebase App Hosting
6+
7+
[Firebase App Hosting](https://firebase.google.com/docs/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.
8+
9+
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.
10+
11+
## The symptom: SSR silently downgrades to CSR
12+
13+
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.
14+
15+
There is no error in the deploy output and no error in the browser. The only trace is a line in the server logs (`firebase functions:log`), which nothing prompts you to check.
16+
17+
## The 30-second check to run after every deploy
18+
19+
Pick an SSR route (not an SSG/prerendered one) and fetch it:
20+
21+
```bash
22+
curl -s https://YOUR-SITE/ | grep ng-server-context
23+
```
24+
25+
Angular's server renderer stamps a `ng-server-context` attribute on the app's root element:
26+
27+
- `ng-server-context="ssr"` - the route was server-rendered (SSR). This is what you want.
28+
- `ng-server-context="ssg"` - the route was prerendered (SSG). Fine in itself, but not the SSR path this guide is about.
29+
- **No `ng-server-context` at 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.
30+
31+
## Why it happens
32+
33+
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.
34+
35+
<!--
36+
Maintainer note (2026-07-19): the two-step fix below is a workaround for a lag in
37+
Firebase App Hosting's build image. The underlying fix (injecting the full
38+
X-Forwarded-* set into NG_TRUST_PROXY_HEADERS) is already merged upstream in
39+
GoogleCloudPlatform/buildpacks (commit 9346e60b, 2026-06-17) but had not reached
40+
production builders as of 2026-07-19. Once it ships, `npm update` alone suffices
41+
and the trustProxyHeaders step becomes optional; revisit and simplify this guide.
42+
-->
43+
44+
## The Fix
45+
46+
Two steps are needed together.
47+
48+
**1. Update Angular to the latest patch:**
49+
50+
```bash
51+
npm update @angular/core @angular/ssr
52+
```
53+
54+
**2. Turn on proxy-header trust when you create the server engine.** In `src/server.ts`, pass `trustProxyHeaders: true` to `AngularNodeAppEngine`:
55+
56+
```ts
57+
import { AngularNodeAppEngine } from '@angular/ssr/node';
58+
59+
const angularApp = new AngularNodeAppEngine({
60+
trustProxyHeaders: true,
61+
});
62+
```
63+
64+
Redeploy, then run the 30-second check above. You should now see `ng-server-context="ssr"`.
65+
66+
### Why both steps
67+
68+
The two steps address different halves of the same handshake, and neither alone is enough:
69+
70+
- Recent Angular patches let the `trustProxyHeaders` engine option take effect on App Hosting; on older patches a platform environment variable wins instead, so the code option is ignored. The `npm update` gets you onto a patch where the option is honored.
71+
- Even on the latest patch, you still have to *set* the option, so App Hosting's proxy headers are trusted.
72+
73+
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](https://firebase.google.com/docs/app-hosting/troubleshooting#angular-proxy-trust).
74+
75+
> 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.
76+
77+
## Related Angular documentation
78+
79+
- [Angular SSR guide](https://angular.dev/guide/ssr)
80+
- [Configuring trusted proxy headers](https://angular.dev/best-practices/security#configuring-trusted-proxy-headers)

0 commit comments

Comments
 (0)