skip to content

In Angular's @angular/ssr, what do RenderMode.Server, RenderMode.Client and RenderMode.Prerender do, and where do you assign them?

level: juniorimportance: must knowfreq 55%

answer

  1. one list, one mode per path
  2. request time, build time, or browser
  3. ServerRoute objects in app.routes.server.ts
  4. provideServerRendering(withRoutes(...))

basics

~10 s

Each ServerRoute passed to withRoutes() sets a renderMode: Server renders HTML on every request, Prerender renders it once at build time, and Client returns the unrendered shell so the browser renders the page.

solid answer

~40 s

`@angular/ssr` lets every route choose how its HTML is produced. You declare a `ServerRoute[]` list (by convention in `app.routes.server.ts`) and register it with `provideServerRendering(withRoutes(serverRoutes))` in the server config. `RenderMode.Server` runs Angular on the server for each request, so the page may depend on cookies or headers. `RenderMode.Prerender` renders at build time into static HTML that is served as is, so it must be the same for every visitor. `RenderMode.Client` does no server rendering: the server returns the client-side shell and the browser renders it like a plain single-page app. Paths are relative, `:param` and `**` are allowed, and `ng add @angular/ssr` generates a single `**` entry set to `Prerender`.

code

ts · 16 lines
ts
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideServerRendering, RenderMode, ServerRoute, withRoutes } from '@angular/ssr';
import { appConfig } from './app.config';

export const serverRoutes: ServerRoute[] = [
  { path: '', renderMode: RenderMode.Prerender },
  { path: 'pricing', renderMode: RenderMode.Prerender },
  { path: 'account/**', renderMode: RenderMode.Client },
  { path: '**', renderMode: RenderMode.Server },
];

const serverConfig: ApplicationConfig = {
  providers: [provideServerRendering(withRoutes(serverRoutes))],
};

export const config = mergeApplicationConfig(appConfig, serverConfig);

go deeper

for a junior

Recall the three RenderMode values, what each one does with the HTML, and that they are assigned in a ServerRoute list registered with provideServerRendering(withRoutes()).

for a middle

Explain how a URL is matched to a server route, why most lists end with a ** entry, and what build-time route validation reports.

for a senior

Show you can predict what breaks when a route has the wrong mode, such as a request-dependent page prerendered once for everyone or a shell page returned for an indexed route.

for a principal

Frame the mode as a per-route trade-off between build time, server cost, freshness and code constraints, and say how the default ** entry should be chosen for the whole app.

## What a server route is In an Angular application that uses **`@angular/ssr`**, rendering is decided **per route**, not once for the whole app. The decision lives in a list of **`ServerRoute`** objects, separate from the Angular Router's own `Routes` array. By convention the list sits in `app.routes.server.ts`, and it is registered in the server-only application config: - `provideServerRendering(...)` sets up server rendering for the app (it lives in `app.config.server.ts`, which is merged with the browser config through `mergeApplicationConfig`). - `withRoutes(serverRoutes)` is the feature that hands it the `ServerRoute[]` list. - Each entry has a `path` and a `renderMode`, and may add `headers` and (except for prerendered entries) a `status`. The Router still decides *which component* a URL shows. The server route only decides *where and when that component's HTML is produced*. ## The three render modes | `RenderMode` | When HTML is produced | What the server returns | Can read the request? | | --- | --- | --- | --- | | `RenderMode.Server` | On every request | Freshly rendered HTML | Yes, through `inject(REQUEST)` | | `RenderMode.Prerender` | Once, at build time | The stored HTML file, with an `ETag` | No, there is no request at build time | | `RenderMode.Client` | In the browser | The client-side shell (`index.csr.html`) | No, Angular does not run on the server | Key consequences: - A **Prerender** page is identical for every visitor, so it must not depend on who is asking. - A **Server** page can use cookies, headers or the URL's query, and costs a render per request. - A **Client** page behaves like a classic single-page app: the browser downloads the bundle and renders it. The route's configured `headers` and `status` are still applied to that shell response. ## Registering the list ```ts // app.routes.server.ts import { RenderMode, ServerRoute } from '@angular/ssr'; export const serverRoutes: ServerRoute[] = [ { path: '', renderMode: RenderMode.Prerender }, { path: 'pricing', renderMode: RenderMode.Prerender }, { path: 'account/**', renderMode: RenderMode.Client }, { path: '**', renderMode: RenderMode.Server }, ]; ``` `ng add @angular/ssr` (or `ng new --ssr`) generates this file with a single entry, `{ path: '**', renderMode: RenderMode.Prerender }`, so a fresh project prerenders everything until you change it. If `provideServerRendering()` is called without `withRoutes()`, routes are also treated as prerendered. ## How a URL finds its entry The engine builds a tree from the server route paths and walks it one URL segment at a time: 1. A **literal segment** (`pricing`) is tried first. 2. A **parameter segment** (`:id`, stored internally as a single-segment wildcard) is tried next. 3. A **`**`** entry catches whatever is left at that level. So `pricing` wins over `**` because it is more specific at that segment, not because of where it appears in the array. ## Build-time validation Route extraction runs when the app is built (and when the dev server first handles a request). It reports configuration errors instead of guessing: - A server route `path` must be relative: `'/pricing'` is rejected because it starts with a slash. - Every Angular route must be matched by some server route, which is why most lists end with a `**` entry. - Every server route other than a `**` catch-all must correspond to a real Angular route, so a typo in a path is caught. - A route defined with a custom `matcher` cannot use `RenderMode.Prerender`. ## Choosing at a glance For a marketing site with a signed-in account area, the usual first cut is: static marketing pages as `Prerender`, pages that depend on the request as `Server`, and the account area as `Client` when search engines never see it and server rendering it would only add per-user work. Which mode fits a whole portfolio of routes is a strategy decision in its own right; the point to recall here is that the choice is made **per path, in one list, with three values**.

  • Does the position of an entry in the ServerRoute array decide which one wins?
    Not for different paths. The engine matches segment by segment: a literal segment such as `pricing` is tried first, then a `:param` segment, then `**`. So `pricing` beats `**` wherever the two appear in the array; specificity decides.
  • What does the server still send for a RenderMode.Client route?
    The client-side shell, `index.csr.html`, with the route's configured `headers` and `status` applied. No Angular code runs on the server for that request, so tokens such as `REQUEST` are never provided and the browser does all the rendering.
  • What stops a typo in a server route path from going unnoticed?
    Route extraction at build time. It reports a server route that matches no Angular route, an Angular route that no server route covers, and a path that starts with a slash, so the mistake becomes a build error rather than a silent default.

A print shop with three counters: posters printed overnight and handed over from the shelf (Prerender), custom prints made while you wait (Server), and a blank sheet plus a pen so you draw it yourself (Client).

saying these in an interview costs you the question

  • Prerendered pages are rendered again on the server for every request
  • Render modes are set with a renderMode property on the Router's Routes array
  • A RenderMode.Client route is excluded from the server and answers 404
  • Server route paths should start with a slash, like '/pricing'
  • A page showing the signed-in user's name is fine to prerender