SSR returns: the server renders HTML again
- date
- category
- Frontend
- reading
- 4 min / 719 words
CSR gave the frontend full control over the application, but the first load started to have a longer and longer critical path.
The browser had to download JavaScript, start the runtime, discover data dependencies and only then produce the UI.
The natural answer was simple:
render the first view earlier, on the server.
SSR comes back, but in a different form
The classic server-rendered web worked like this:
request
|
v
server
|
v
HTML
|
v
browser
Modern SSR adds one more stage:
request
|
v
server render
|
v
HTML
|
v
browser shows content
|
v
JavaScript loads
|
v
application takes over
That is an important difference.
The server does not render the page so that the frontend stops existing.
It renders it so that the first view exists earlier.
Afterwards the browser can still behave like an SPA.
Why do the same work on the server
Assume the user opens:
/products/42
In pure CSR it can go like this:
HTML shell
|
v
JS
|
v
router
|
v
fetch product
|
v
render
In SSR:
request /products/42
|
v
server fetches product
|
v
server renders HTML
|
v
browser receives content
The user can see the product name, price and description before the whole application runtime has started.
That shortens the path to the first content.
But it does not mean the client cost disappears.
SSR does not remove JavaScript
This is one of the most important points.
If the application is to be interactive later, the browser still often has to download:
the framework
components
routing
event handling
application state
the code responsible for further interactions
So SSR is not:
server instead of browser
It is rather:
server first
browser later
In practice some of the work may be done twice.
The server produces HTML, and the client later reconstructs the component runtime needed for the application to keep working.
That process will become a separate problem we will later call hydration.
TTFB starts to matter again
In CSR the server can respond almost immediately:
<div id="app"></div>
That gives a great TTFB, but says little about when the actual content shows up.
SSR moves work in front of the response.
request
|
v
database
|
v
business logic
|
v
render
|
v
response
That can increase Time to First Byte.
And here the first real trade-off appears:
CSR
-> fast empty response
-> more work later
SSR
-> more work before the response
-> more finished content earlier
That is why the statement:
SSR is faster
is too simple.
Faster at which point of the path?
A commercial example: the product detail page
An online store is a good case for SSR.
The user lands on a product page straight from a search engine.
The most important data is already known during the request:
name
price
photo
availability
description
The server can fetch it and immediately produce:
<h1>Mechanical Keyboard</h1>
<p>129 zł</p>
<p>In stock</p>
The browser does not have to wait for the framework to fetch and perform its first render.
At the same time an interactive cart, product variants or recommendations can start up later.
Modern frameworks such as Next.js, Nuxt or SvelteKit use this model as one of the available strategies.
A curiosity
SSR can become a CPU problem even when a single render is fast.
Assume:
render = 20 ms CPU
With one user that is practically nothing.
With thousands of requests per second the situation looks different.
If every request requires rendering:
request
request
request
request
request
|
v
limited CPU cores
a queue starts to form.
The user's latency is then not just:
render time
but:
queue time + render time
That is why under heavy traffic optimising SSR becomes a problem of concurrency, cache and capacity planning, not only of the framework.
Personalisation complicates the cache
SSR fits dynamic data very well.
But the more the response depends on the request, the harder it is to cache.
An example:
/products/42
If every user sees the same HTML, the response can easily live on a CDN.
But if the page depends on:
user
country
currency
experiment
session
pricing tier
the number of possible variants grows.
In practice you have to decide what really needs to be dynamic.
Often only a small fragment of the page is personalised, while the rest could be shared by everyone.
That question will later lead to more granular rendering models.
SSR can create a waterfall too
Moving the render to the server does not remove data dependencies.
It can only move them somewhere else.
Badly written SSR:
fetch user
|
v
fetch product
|
v
fetch reviews
|
v
fetch recommendations
|
v
render
means the user waits for the whole sequential chain before receiving the first byte of HTML.
So the server can create exactly the same waterfall we used to have in the browser.
Except now the waterfall blocks the entire response.
SSR solves one problem and creates the next
Moving rendering back to the server improved the first display of content, but a question appeared:
what next?
We have finished HTML.
But the browser still needs JavaScript for the buttons to work, for state to be current and for navigation to stay client-side.
So we have to connect:
HTML generated on server
with:
application runtime in browser
That connection is neither free nor trivial.
Which is exactly why the next big element of modern rendering architecture became hydration.