All articles & videos

Rendering for enterprise apps: CSR, SSR or both?

or for enterprise and React apps: when server rendering pays, what costs, , , and .

Adil Ben Moussa
08 November 2026· 5 min
Rendering for enterprise apps: CSR, SSR or both?

A team adds server-side rendering to an internal banking dashboard. Every page needs a logged-in user, no search engine will ever see it, and now there is a server to run, patch and audit. The first load is a little faster. The bill and the are a lot bigger.

Many logged-in enterprise apps do not need server rendering. Some parts of the same organisation do. The decision is per page, not per company.

CSR for the app, SSR or prerender for the public pages

Angular's own guide is honest about both sides. Client-side rendering "has the simplest development model", and "the server does not need to do any work to render a page beyond serving static JavaScript assets". It "generally has worse performance", and "may negatively affect" for pages that crawlers index. Server-side rendering "may increase server hosting costs" and "requires you to author code that does not strictly depend on browser APIs".

Image: tt-tech.io, adapted from web.dev, Rendering on the web, CC BY 4.0.

So the line I draw is the login. Behind it (dashboards, back office, case handling), CSR. In front of it (product pages, rates, branch finder), what is the same for everyone and server-render what changes per request. web.dev says the same in general terms: "you can always choose to use server-side rendering for some pages and not others." Angular supports that per route with RenderMode.Client, Prerender and Server. Often the public site and the app are simply two apps.

Hydration is the price of SSR

Server HTML still has to become an app. Angular's hydration reuses the server-rendered DOM. Without it, Angular destroys and re-renders that DOM, which "may result in a visible UI flicker". But "event listeners are not attached until hydration completes", which is why Angular ships .

goes further: @defer blocks with hydrate triggers (idle, viewport, interaction) control when Angular loads and hydrates that part, which "can produce smaller initial bundles". It "is enabled by default when you use provideClientHydration()". Worth it on a public page with a heavy widget. Not a reason to add SSR to a dashboard.

@defer for the heavy widgets

Charts, rich-text editors and PDF viewers rarely need to be in the first . @defer blocks "reduce the initial bundle size of your application by deferring the loading of code that is not strictly necessary for the initial rendering of a page".

One enterprise catch: deferred dependencies "must be standalone". Code that still lives in an NgModule cannot be deferred. Your legacy corner decides what you can lazy-load.

In an old codebase, the legacy corner decides what you can lazy-load.

Lazy routes along team lines

Lazy routes split the bundle where your teams already split the code: one feature library, one route, one chunk. Angular notes that lazy routes remove "large portions of JavaScript from the initial bundle", recommends eager loading "for primary landing page(s)", and warns that deeply nested lazy loading "can significantly impact performance".

Zoneless and OnPush by default

Two defaults changed what triggers rendering in Angular. Zoneless "is the default in Angular v21+". "is the default change detection strategy in Angular (since v22)". Rendering now follows notifications: a read in a template changes, an input is set, a template listener runs, or code calls markForCheck(). Legacy code that mutated a field and waited for to notice now needs one of those.

React Server Components

On the React side of the house, Server Components render "ahead of time, before bundling, in an environment separate from your client app or SSR server". They suit content-heavy pages that read data on the server. They usually come with , which move a security line into the frontend repo: their arguments "are fully client-controlled", and React tells you to "validate that the logged-in user is allowed to perform that action". In a regulated app, that function is a public endpoint and gets reviewed like one.

On Atlas Bank

Atlas Bank is client-rendered on purpose: logged-in banking, no SEO, no Node server. It is zoneless (the repo's AGENTS.md says "Never add zone.js") and every feature is a lazy route in apps/atlas/src/app/app.routes.ts, the legacy screens included. Two of the five routes:

  {
    path: 'accounts',
    loadChildren: () =>
      import('@atlas/feature-accounts').then((m) => m.accountsRoutes),
  },
  {
    path: 'transfers',
    loadChildren: () =>
      import('@atlas/legacy-transfers').then((m) => m.LegacyTransfersModule),
  },

The legacy NgModule can still be a lazy route; it just cannot sit inside a @defer block. Even the dashboard on '' is lazy, against Angular's advice for landing pages. In a small demo the difference is small; in a real app I would measure it.

What I left out, and why

  • , , as separate items. Framework-specific variations on "send less JavaScript".
  • . A content-site technique, best known from .
  • versus . A framework debate that rarely changes a product decision.

On Monday

  • List your routes and mark each one: behind the login or not. Only the second group is an SSR question.
  • Find your heaviest widget . Wrap it in @defer (on viewport) and compare the initial bundle.
  • Search for components that still rely on Zone.js to notice a change. They are your zoneless backlog.

Next: Part 5, on performance.

For your coding agent

The rules of this article, written for an agent. Paste them into your AGENTS.md, save them as a skill, or point your agent at the file.

For your coding agent Download .md
# Rendering rules for enterprise apps

Source: https://tt-tech.io/blog/enterprise-frontend-rendering-csr-ssr
For Angular and React frontends. Add to AGENTS.md or save as a skill. Your project's own rules win where they differ.

## Rules

1. Decide rendering per page, not per app. The line is the login: pages behind it use client-side rendering (CSR) by default.
2. Do not add server-side rendering (SSR) to dashboards, back office or other pages behind a login by default. It adds a server to run, patch and audit.
3. For public pages, prerender what is the same for everyone and server-render what changes per request. In Angular, set it per route with `RenderMode.Client`, `RenderMode.Prerender` and `RenderMode.Server`.
4. Code that runs under SSR must not depend on browser APIs. Keep hydration on with `provideClientHydration()`, so Angular reuses the server DOM instead of re-rendering it.
5. Use incremental hydration (`@defer` with `hydrate` triggers such as idle, viewport or interaction) for a heavy widget on a public page. It is not a reason to add SSR.
6. Put heavy widgets below the fold (charts, rich-text editors, PDF viewers) in `@defer`, for example `@defer (on viewport)`.
7. Deferred dependencies must be standalone. Code that still lives in an `NgModule` cannot go in `@defer`; load it through a lazy route instead.
8. Split lazy routes along team lines: one feature library, one route, one chunk. Load primary landing pages eagerly, as Angular recommends, and avoid deeply nested lazy loading.
9. Never rely on Zone.js to notice a change. Trigger rendering through a signal read in the template, an input, a template listener, or `markForCheck()`.
10. Treat every React Server Function (`'use server'`) as a public endpoint. Its arguments are client-controlled: validate them and check that the logged-in user may perform the action.

## Before you say done

- For every new or changed route, state whether it is behind the login and which render mode it uses.
- When you add or move a `@defer` block or a lazy route, run the production build and report the initial bundle size before and after.
- Search changed components for plain fields the template reads and code mutates. Make them signals, or name the notification that renders them.
- Search the diff for `'use server'`. For each function, name where it validates its arguments and checks the user.

Sources

  1. Angular, Server-side and hybrid-rendering, docs
  2. web.dev, Rendering on the web
  3. Angular, Hydration, docs
  4. Angular, Incremental Hydration, docs
  5. Angular, Deferred loading with @defer, docs
  6. Angular, Lazy-loaded routes, docs
  7. Angular, Angular without ZoneJS (Zoneless), docs
  8. Angular, Skipping component subtrees, docs
  9. React, Server Components, docs
  10. React, 'use server' directive, docs
Share this article