SSR & Hydration

Applies to

The Angular adapter supports Angular SSR and Incremental Hydration as of Angular 18 — both for hosts and remotes. Most of the wiring is done by the init schematic when it detects an SSR-enabled project; this page documents what it generates and the moving parts behind it.

Enabling SSR

Add SSR to your Angular project the usual way:

ng add @angular/ssr --project shell

Then run the federation init (or re-run it). When the schematic sees build.options.ssr.entry it adds two SSR-specific things:

  1. Sets ssr: true on the federation build target. The builder uses this flag to route externals through Angular's externalDependencies instead of an esbuild plugin (the SSR build path doesn't run that plugin).
  2. Splits main.server.ts into a federation bootstrap (main.server.ts) plus the original Angular SSR bootstrap (bootstrap-server.ts).

The Node Bootstrap

The schematic generates a main.server.ts that calls initNodeFederation from @softarc/native-federation-node before pulling in the Angular SSR app:

// projects/shell/src/main.server.ts (dynamic-host)
import { initNodeFederation } from '@softarc/native-federation-node';

console.log('Starting SSR for Shell');

(async () => {
  await initNodeFederation({
    remotesOrManifestUrl: '../browser/federation.manifest.json',
    relBundlePath: '../browser/',
  });

  await import('./bootstrap-server');
})();

For static hosts the manifest is inlined; for remotes only the relBundlePath is set:

// remote variant
import { initNodeFederation } from '@softarc/native-federation-node';

(async () => {
  await initNodeFederation({ relBundlePath: '../browser/' });
  await import('./bootstrap-server');
})();

Your existing SSR setup — Express, prerender, Vite middleware, anything ng add @angular/ssr generated — moves to bootstrap-server.ts unchanged. The schematic also rewrites the Express setup to:

fstart.mjs

For SSR builds the federation builder writes a tiny fstart.mjs next to the server output (dist/<project>/server/fstart.mjs). It's a small Node bootstrap that ensures the federation runtime starts before Angular's server entry, mirroring the main.tsbootstrap.ts split on the browser side. You don't need to invoke it directly — point your runtime at it the same way you'd point at server.mjs.

Externals on the Server

On the browser side, externals are excluded from the Angular bundle by an esbuild plugin the adapter installs. That plugin is bypassed for SSR builds, so the adapter instead sets options.externalDependencies = externals on the underlying Angular target. The result is the same — Angular doesn't pre-bundle anything Native Federation will load at runtime — but uses Angular's first-class hook.

One important consequence: @angular/core infers ngServerMode from the bundling step. Because Native Federation reuses the same shared @angular/core bundle on the server and in the browser, the adapter patches node_modules/@angular/core/fesm2022/core.mjs with a small runtime check:

if (typeof globalThis.ngServerMode === 'undefined')
  globalThis.ngServerMode = (typeof window === 'undefined') ? true : false;

The patch is applied automatically by the adapter on every build. Treat it as an implementation detail.

Manifests & CORS

Related