Why Your Server Is Paying for Your CSS Choices

Server-side rendering makes some previously invisible costs very visible. This post explores how styling can end up on the server's hot…

Styling is rarely something teams worry about.

In a typical single-page application, styles are chosen early, work reliably, and fade into the background. Performance conversations focus on JavaScript bundles, network requests, or backend latency. Styling is usually treated as a solved problem.

That tends to change once server-side rendering enters the picture.

This post describes a pattern that often shows up during SPA-to-MPA migrations: a decision that once felt incidental starts to affect throughput, latency, and CPU usage once it moves onto the server’s critical path.

A change in where work happens

Moving to a server-rendered multi-page application is often driven by sensible goals — better caching behaviour, more predictable rendering, faster first loads.

Alongside those benefits comes a shift in responsibility.

In an SPA, much of the work happens once per session in the browser. With SSR, rendering work happens per request on the server. Markup generation, data shaping, and styling all run as part of handling traffic.

Under enough load, even small amounts of repeated work start to matter.

When nothing is broken, but things slow down

These problems rarely show up as failures. Pages render correctly. Styles apply as expected. Logs stay quiet. Instead, teams notice gradual signals:

  • CPU usage increases under load
  • response times stretch
  • throughput stops scaling as expected

The initial focus is usually on infrastructure, caching, or backend queries. Those areas often help, but sometimes a gap remains. At that point, attention turns to parts of the stack that rarely attract scrutiny.

Rendering is one of them.

An unremarkable change with visible effects

Styling choices are often carried forward without much discussion. A common situation looks like this:

  • an existing SPA uses SCSS or CSS Modules
  • a new SSR codebase adopts a CSS-in-JS library as a modern default
  • the change feels stylistic rather than architectural

For a while, everything works as expected. Developer experience improves. Components feel easier to reason about.

Only once SSR traffic increases does the cost of that decision become visible. Style generation now runs as part of every request, rather than being front-loaded or handled on the client.

That raises a practical question: how much work is the server doing purely to produce styles?

Turning suspicion into a benchmark

Rather than relying on intuition, it helps to isolate the variable.

To do that, I put together a small, self-contained repository that compares SSR performance when the only meaningful difference is how styling is handled:

👉 https://github.com/dp-lewis/ssr-performance-sc-vs-scss

The goal isn’t to recreate a full production system. It’s to reduce the problem to something observable.

The repo includes:

  • two SSR application variants with identical markup and content
  • one using CSS Modules with SCSS
  • one using Styled Components
  • identical endpoints and load-testing configuration
  • simple and complex rendering scenarios

A benchmark script uses autocannon to measure request throughput and latency under load.

What’s being measured

Two scenarios are tested:

Simple

  • 100 items
  • static styling
  • baseline SSR overhead

Complex

  • 1000 items
  • per-item dynamic styles (colour and opacity)
  • intended to stress runtime style generation

Both approaches are implemented idiomatically. CSS Modules rely on build-time styles with inline values for dynamics. Styled Components interpolate dynamic props into templates and emit server-side style tags.

What the results show

Across repeated runs on the same machine, a consistent pattern emerges.

In the simple scenario, CSS Modules handle more requests per second with lower average latency. The difference is noticeable, but modest.

In the complex scenario, the gap widens significantly.

Averaged across multiple runs:

Simple

  • CSS Modules: ~5,090 req/s, ~19 ms latency
  • Styled Components: ~3,990 req/s, ~25 ms latency

Complex

  • CSS Modules: ~568 req/s, ~175 ms latency
  • Styled Components: ~344 req/s, ~289 ms latency

Once dynamic styling becomes prominent, the difference moves from tens of percent to something closer to a halving of throughput.

The exact numbers matter less than the shape of the results.

Why this shows up under SSR

Generating styles at render time involves real work:

  • computing dynamic values
  • generating class names
  • collecting and serialising style output
  • repeating that work for every request

When rendering happens on the client, much of this cost is hidden. With server-side rendering, it sits directly on the request path and scales with traffic.

Approaches that rely more on build-time output tend to place less work inside that path, which can translate into lower CPU usage and higher throughput under load.

A broader takeaway

Architectural shifts have a way of surfacing costs that were always present but easy to ignore.

Moving from client-heavy rendering to server-heavy rendering reshapes performance boundaries. Decisions that once sat comfortably outside those boundaries can move into focus without much warning.

Styling is one example of that pattern.

Wrapping up

Performance issues often appear far from where teams expect them.

Sometimes they show up in parts of the system that have worked quietly for years, only becoming visible when the surrounding architecture changes.

Server-side rendering has a habit of doing that.