skip to content

Which Angular routes should not use RenderMode.Prerender, and what goes wrong when a request-dependent page is prerendered?

level: seniorimportance: should knowfreq 38%

answer

  1. built once for everyone
  2. REQUEST is null
  3. no status, no cookies
  4. query strings don't pick files
  5. stale until next build

basics

~20 s

Pages that depend on the visitor, cookies, headers, query strings or fast-changing data should not be prerendered. At build time REQUEST is null, so the page renders its anonymous, default version once, and every visitor receives it until the next build.

solid answer

~40 s

Prerendering renders a route once during `ng build`, with no incoming request: `inject(REQUEST)` returns `null`, there are no cookies or headers, and the file is chosen by path, so a query string does not select different HTML. A signed-in dashboard therefore bakes in its logged-out state, a search page bakes in its empty-query result, and a price list bakes in build-day prices. Prerender server routes also cannot set a `status`, so a not-found page served as a prerendered file answers `200`. The symptoms: the wrong content flashes and is then replaced after hydration, or stays wrong for crawlers, or data goes stale until the next deploy. Keep `RenderMode.Prerender` for pages identical for everyone and stable between builds, and give request-dependent routes `RenderMode.Server` or `RenderMode.Client`.

go deeper

for a junior

Remember that prerendered pages are built once for everyone, so anything specific to a visitor cannot be in them.

for a middle

Explain that REQUEST is null at build time, that the file is chosen by path, and that prerender routes cannot set a status.

for a senior

Classify routes by what they read from the request and how fresh their data must be, and pick Server, Client or a split page accordingly.

for a principal

Govern render-mode choices across a growing app so request-dependent pages never drift into the default Prerender catch-all.

## The core constraint `RenderMode.Prerender` renders a route **once**, during the build, for **everyone**. The render has no request behind it: - `inject(REQUEST)` returns `null` during static generation, as the token's own documentation states; - there are no cookies, no `Authorization` header, no `Accept-Language`, no client IP; - the page is chosen by **path**; a query string does not select a different file; - the route's server configuration has no `status` property for prerendering, so the status cannot vary. Anything the page would normally read from the request is absent or at its default value when the HTML is produced. ## What goes wrong, by page type | Page | What gets baked in | What users see | |---|---|---| | Signed-in dashboard | The logged-out or empty state | A flash of wrong content until client code replaces it | | Search results by `?q=` | The result for no query | The same HTML for every query until the browser takes over | | Localized page chosen by `Accept-Language` | The build machine's default | Every visitor gets one language in the HTML | | Stock levels or prices | Values from build day | Stale data until the next deploy | | "Not found" for a bad id | A normal page | A `200` status for missing content | Each case also affects crawlers, which see only the baked-in HTML. ## Why hydration does not save you After the prerendered HTML loads, the app boots and may fetch fresh, user-specific data. That fixes the view eventually, but: 1. The first paint shows the wrong content, which is often worse than a loading state. 2. If the browser's first render differs structurally from the prerendered DOM, hydration reports mismatches. 3. Crawlers and link previews keep the wrong version. ## Choosing a better mode - **`RenderMode.Server`** renders per request and can read `REQUEST`, cookies and headers, and set a `status`. Use it for personalised pages, query-driven pages and correct not-found responses. - **`RenderMode.Client`** sends the client shell. Use it for signed-in areas that crawlers never see. - **Split the page.** Prerender the shared shell and render the personal part in the browser after hydration, for example a documentation page with a prerendered article and a client-rendered "your progress" widget. ## Build-time traps that look like request-time bugs - Components that call `Date.now()` or pick random items bake one value into the file. - Environment-specific configuration read during the build reflects the build environment, not the one serving traffic. - A prerendered route whose data source is unreachable during the build fails the build, or renders an error state into the file if the error is caught. ## The silent default The CLI's SSR schematic generates a single `{ path: '**', renderMode: RenderMode.Prerender }` server route. That default is convenient, and it is also how request-dependent pages end up prerendered by accident: 1. A developer adds an `account` route to the client routes. 2. It has no parameters, so it matches the catch-all and is prerendered without any error. 3. The build writes `account/index.html` in its signed-out state, and nobody notices until a user reports the flash. Parameterized routes fail loudly because `getPrerenderParams` is missing; parameterless ones do not. A safer server routes file lists the request-dependent sections explicitly, for example `account/**` with `RenderMode.Server` or `RenderMode.Client`, before the catch-all, and code review checks new routes against it. ## A quick test for each route Ask three questions: 1. Would two visitors, at the same moment, see different HTML? If yes, do not prerender. 2. Does the data change more often than you deploy? If yes, prerendering serves stale pages. 3. Does the page need a status other than `200`? If yes, it needs a server render. If all three answers are no, prerendering is a strong fit.

  • A prerendered product page shows a not-found message for a removed id but returns 200. How do you fix the status?
    Prerendered files are served as they are, and prerender server routes cannot set a `status`. Either remove the id from `getPrerenderParams` and let `PrerenderFallback.None` hand the request to a 404 handler, or render that route with `RenderMode.Server`, where the response status can be set per request.
  • Can a prerendered page still show personalised content?
    Yes, if the personal part is rendered in the browser after hydration, for example a widget that loads user data once the app is running. The prerendered HTML must contain only the shared parts, and the placeholder for the personal part must be the same on server and browser to hydrate cleanly.

saying these in an interview costs you the question

  • Prerendered pages can read cookies because they run on the server.
  • Hydration fixes any wrong prerendered content, so it doesn't matter.
  • A query string selects a separate prerendered file per value.
  • You can set status: 404 on a prerendered server route.
  • Prerendering is always faster, so every route should use it.