After an upgrade, an Angular SSR app fails every request with NG0401 Missing Platform. What is BootstrapContext, and how must main.server.ts change?
answer
- no global platform on the server
- a platform per request
- third argument to bootstrapApplication
- context.platformRef
basics
~10 sBootstrapContext carries the platformRef the server created for this request. The server bootstrap function must accept it and pass it as the third argument: bootstrapApplication(App, config, context).
solid answer
~40 sSince Angular 20.3 server bootstrapping no longer relies on a global platform injector. For each render, the server creates a fresh platform holding that request's document, URL and, for `RenderMode.Server`, the `REQUEST`, `REQUEST_CONTEXT` and `RESPONSE_INIT` tokens, then calls the default export of `main.server.ts` with a `BootstrapContext` (`{ platformRef }`). The export must be `(context: BootstrapContext) => bootstrapApplication(App, config, context)`. If the context is dropped, Angular's application factory finds no platform in server mode and throws NG0401. The update migration rewrites standard entry files; hand-written wrappers and shared bootstrap helpers are what usually break. On the server `getPlatform()` now returns `null`, so code that relied on it must move to per-request DI.
code
ts · 8 linesimport { BootstrapContext, bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app';
import { config } from './app/app.config.server';
const bootstrap = (context: BootstrapContext) =>
bootstrapApplication(App, config, context);
export default bootstrap;go deeper
Recall that main.server.ts must accept a BootstrapContext and pass it to bootstrapApplication as the third argument.
Explain what the context carries, which request-scoped values live on the per-request platform, and why the missing platform surfaces as NG0401.
Diagnose the failure in custom entry points and shared bootstrap helpers, and find code that relied on a global platform or on module-level state across requests.
Use the change to audit how the server isolates requests, and set rules for where request-scoped state may live in a long-running SSR process.
## The symptom After moving an application onto a current Angular release, every server-rendered request fails with **NG0401, "Missing Platform"**. The development message says that `bootstrapApplication` was called on the server without a `BootstrapContext`. Client-side rendering still works, which makes the failure look like a server bug. ## What changed The server-side bootstrapping process was changed to **stop relying on a global platform injector**. The change shipped as a breaking change in **Angular 20.3** (and was back-ported to the 19.2 and 18.2 patch lines); v21 and v22 carry it. Since then: - the server bootstrap function receives a **`BootstrapContext`** and must forward it; - `bootstrapApplication(rootComponent, config, context)` takes that context as its third argument; - on the server, `getPlatform()` returns `null` and `destroyPlatform()` does nothing. `BootstrapContext` is exported from `@angular/platform-browser` and has one field, `platformRef`: a reference to a platform the server has already created. ## Why a context is needed For each render, the server side (the `@angular/ssr` app engine, or `renderApplication` from `@angular/platform-server`) creates a **fresh platform** for that request and calls your default export with `{ platformRef }`. The platform is where request-scoped values live: | Provided on the per-request platform | Why it must be per request | | --- | --- | | The server `DOCUMENT` built from the index HTML | Each render mutates its own DOM | | The URL being rendered | The router starts from it | | `REQUEST`, `REQUEST_CONTEXT`, `RESPONSE_INIT` (for `RenderMode.Server`) | They describe one request only | If the application does not attach to that platform, it has no document, no URL and no request to render against. In server mode, Angular's application factory checks for the platform reference and throws NG0401 when it is missing. ## The fix ```ts // main.server.ts import { BootstrapContext, bootstrapApplication } from '@angular/platform-browser'; import { App } from './app/app'; import { config } from './app/app.config.server'; const bootstrap = (context: BootstrapContext) => bootstrapApplication(App, config, context); export default bootstrap; ``` The update migration rewrites `main.server.ts` this way, and `ng add @angular/ssr` generates it. The failures seen in practice come from: 1. hand-written server entry points the migration did not recognise; 2. wrappers that call `bootstrapApplication` inside another function and drop the argument; 3. shared bootstrap helpers used by both `main.ts` and `main.server.ts` that never accepted a context. ## What else to check after the change - **Code that called `getPlatform()` on the server** now gets `null`. Anything that stashed state on the platform between requests has to move to per-request DI. - **Module-level state is still shared.** A `useValue` provider evaluated when the server bundle loads keeps its value for every request until the process restarts; use a factory when a value must be computed per request. - **Tests that bootstrap the server entry** must pass a context too, or render through `renderApplication`, which supplies one. ## Why interviewers ask it The question checks whether a candidate understands that a Node process serves **many requests with one loaded bundle**, and that Angular isolates them by building a platform and an application per request. The error code itself is trivia; the reason the context exists is not. ## Quick diagnosis checklist 1. Open `main.server.ts` and confirm the default export takes a `context` parameter. 2. Follow every call path to `bootstrapApplication` and confirm the third argument is that same `context`. 3. Search the server code for `getPlatform()` and for module-level variables that hold request data. 4. Re-run a server render and confirm `inject(REQUEST)` is non-null on a `RenderMode.Server` route.
- Why does a useValue provider in the server config not give each request a fresh value?The server bundle is loaded once and serves many requests, so a value evaluated at load time is shared until the process restarts. A `useFactory` provider runs its factory for each application instance, which is created per request.
- What happens to getPlatform() and destroyPlatform() on the server now?`getPlatform()` returns `null` and `destroyPlatform()` does nothing in a server environment, because no global platform exists there. Code that read the platform between requests has to use the per-request injector instead.
saying these in an interview costs you the question
- NG0401 means the app forgot provideServerRendering()
- One global platform serves every SSR request in current Angular
- The context argument is optional on the server and only affects performance
- getPlatform() still returns the shared server platform
- useValue providers are recreated for every server request