Skip to the solution
HardFrontend12 min read

What is Angular Universal / SSR?

730 views

The problem

Question presented to candidate: "Our marketing site is a client-rendered Angular app and the SEO team says crawlers and link previews see an empty page. What is Angular Universal, or SSR, and what would change if we turned it on?"

What a strong answer should cover:

  • Server-side rendering produces the page HTML on a server (per request) or at build time (prerendering / SSG), so the first paint shows real content.
  • Angular Universal is the old name; since v17 the tooling lives in the CLI as @angular/ssr, added with ng add @angular/ssr.
  • Hydration (provideClientHydration()) lets the browser reuse the server DOM instead of re-creating it, and the HttpClient transfer cache avoids repeating API calls.
  • Render modes per route: RenderMode.Server, RenderMode.Prerender, RenderMode.Client in app.routes.server.ts.
  • SSR-safe code: no window/document access during render; use afterNextRender, isPlatformBrowser, inject(DOCUMENT).
  • Trade-offs: a Node server to operate (unless fully prerendered), time to first byte, and the security setting allowedHosts.

Clarifying questions expected:

  • "Is the content the same for every visitor, or personalised per request?"
  • "Do we already have somewhere to run a Node server, or must this be static hosting?"

Code / implementation expected: Optional β€” the server config files, and a component that is safe to render on the server.

  • ssr
  • universal

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

12 min read Β· 11 sections

Read the code

AngularAn SSR-friendly component: HttpClient transfer cache plus afterNextRender for browser-only work
import { Component, ApplicationConfig, afterNextRender, inject, signal } from "@angular/core";
import { HttpClient, provideHttpClient, withFetch } from "@angular/common/http";
import { provideClientHydration } from "@angular/platform-browser";

@Component({
  selector: "app-root",
  template: `<h1>Products</h1>
    @for (p of products(); track p.id) { <p>{{ p.name }}</p> }
    <small>{{ status() }}</small>`,
})
export class AppComponent {
  products = signal<{ id: number; name: string }[]>([]);
  status = signal("server HTML");
  constructor() {
    inject(HttpClient).get<{ id: number; name: string }[]>("/api/products").subscribe((p) => this.products.set(p));
    // Never runs during SSR: only after the first render in a browser.
    afterNextRender(() => this.status.set("hydrated, width " + window.innerWidth));
  }
}

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withFetch()), provideClientHydration()],
};
AngularThe server files the @angular/ssr ng-add schematic generated (CLI 22.2.3)
// src/app/app.config.server.ts (generated by the @angular/ssr ng-add schematic, CLI 22.2.3)
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering, withRoutes } from '@angular/ssr';
import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';

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

export const config = mergeApplicationConfig(appConfig, serverConfig);

// src/app/app.routes.server.ts (generated)
import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: '**',
    renderMode: RenderMode.Prerender,
  },
];

// src/main.server.ts (generated)
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;

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 96 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What is the difference between ElementRef, TemplateRef, and ViewContainerRef?

Continue