Skip to main content
Stack & tools

Angular 19 SSR in production: what changes, and the traps

Angular's server-side rendering solves a real indexing problem. It creates five more, all of them hit on this site. A field report, without the marketing.

Digi4··9 min read

This site runs Angular 19 with server-side rendering (SSR). That is not a technology demonstration: without SSR, an Angular site whose content comes from an API is a blank page to most crawlers at the moment it matters. What follows is the field report — what SSR genuinely fixes, what it costs, the five traps we hit, and the cases where we do not recommend it.

What it is, in one sentence

In classic client-side rendering, the server sends an empty page and a bundle of JavaScript; the content appears once that runs. In server-side rendering, Node executes the application, produces the complete HTML, sends it, and the browser then "hydrates" that page to make it interactive. The visitor — and the crawler — gets content in the first response.

The problem it solves

A site whose text lives in a database needs that text to be in the HTML that is served. Three things depend on it directly, and all three are measurable:

  • The title and meta description tags, and JSON-LD structured data, present in the response rather than added afterwards.
  • Internal links. A link that exists only in JavaScript is not a link: Google states explicitly that it can only reliably crawl an <a> element with an href. We learned this on our own language switcher, which was a <button> — our French pages pointed at no English page at all.
  • First render, on the connections and devices that are not the developer's.

What it actually costs

The main cost is not development, it is hosting — and that is the most badly anticipated part of it in Morocco. A client-rendered Angular site is just deployed: static files, and any shared hosting will do. An SSR application needs a Node process running permanently, which has to be restarted, monitored and updated.

Many local shared-hosting offers are built for Apache and PHP. Running a persistent Node process on them is sometimes possible and sometimes not, depending on the plan — a question to ask before choosing the architecture, not after acceptance testing. The second cost is skills: you need someone for whom a process that will not restart is not frightening. Our day rates are published on the pricing page; we do not quote a hosting price here because it depends entirely on the plan chosen.

Five traps, all of them hit on this site

1. ng serve does not test your server

This is the most expensive trap, because it creates the illusion that everything works. The Express middleware you write in server.ts — redirects, status codes, sitemap.xml, security headers — is not executed by ng serve. The only way to test it is to build, then run the server that build produces:

npm run build
node dist/frontend/server/server.mjs

Until you have done that, you have verified nothing about your server layer. We validated redirects that "worked" for days before understanding that they had never gone through the code in question.

2. Soft 404s, or how to manufacture ghost pages

When a component calls the API and it fails, the temptation is to show "content not found". Under SSR that page goes out with an HTTP 200: to a search engine that is not an error, it is a thin page to index. An API incident lasting a few minutes is enough to put dozens of phantom pages into the index, and they stay there far longer than the incident did.

The rule we have applied since: an API failure is not a "not found". The component has to distinguish "the API says this does not exist" — a real 404 — from "the API did not answer", which should produce a 503 with a Retry-After, and above all must never be cached.

3. Your SSR is a client of your own API

Every server-rendered page triggers calls to your API, from a single IP address. Our rate limiter — written to protect the API from abuse — started blocking our own rendering server as soon as a crawler walked several pages in a row. The result: incomplete pages served to search engines, and no findable cause on the frontend, because the frontend code was correct.

Two fixes: raise the limit for public routes, and above all exempt the SSR server's egress IP. The second is the real one; the first only moves the threshold.

4. A production build does not show the data you think

Angular substitutes environment files according to the build configuration. A production build therefore points at the production API — including when you run that build on your own machine. We spent a while verifying content changes on a local server that was, in fact, faithfully displaying production data. To verify local content you have to build with the matching configuration; otherwise you are testing someone else's site.

5. HTML caching and security headers do not get along

Caching the rendered HTML is the simplest performance win there is. But if your security policy uses a per-response nonce, a cached page body carries a stale nonce, and the browser then blocks every script on the page. The fix is to store a placeholder in the cached body and substitute the real value at send time, so that the header and the body always carry the same nonce.

When we do not recommend SSR

  • An application behind authentication. A back office has no visitors to rank for. Our own admin interface is deliberately client-rendered: SSR would add nothing there but deployment complexity.
  • Entirely static content. If the text does not come from a database, pre-rendering at build time is simpler and more robust than a running process.
  • Nobody to operate Node. An SSR site whose process falls over on a Friday evening is offline, not degraded. If that on-call does not exist, client-side rendering is the more honest choice.

What we would do differently

One thing, and it would have saved weeks: build and serve the real server from day one, and make HTTP-level verification — status codes, headers, the served title, links present in the HTML — a step in acceptance testing, alongside unit tests. The five traps above have one thing in common: not one is visible from a browser in development, and every one of them shows up in a single curl against the built server.

You can check this site, for that matter: the HTML served contains the titles, the structured data, and the links between the French and English versions.

We build Angular applications and business platforms from Casablanca — see website creation, business platforms and our pricing. Got a project in mind? Request a quote.

Newsletter

Get our upcoming articles straight to your inbox.

Chat on WhatsApp