Konrad Kowalski (rootsher)Principal Platform & Reliability Architect001101010111110101100111110000111001000000100100

SSG: rendering before the user exists

date
category
Frontend
reading
4 min / 700 words

SSR shortens the path to the first content, but it has a cost: rendering happens on the request path.

If a user opens a product page, the server can fetch data, build a component tree and produce HTML. That works well as long as we really need to do that work on every visit.

But what if the content changes once a day?

Then rendering per request starts to look like wasted CPU.

Move the render even earlier

SSR does:

text
request
  |
  v
render
  |
  v
HTML

SSG moves the same render to build time:

text
build
  |
  v
render
  |
  v
HTML artifact
  |
  v
deploy
  |
  v
request
  |
  v
serve file

For the user the difference is enormous.

The server no longer has to run rendering logic for every request. A finished document can sit on a CDN and be returned practically like an ordinary file.

That is Static Site Generation.

SSG is precomputation

The simplest mental model:

text
HTML = render(data)

If data is known in advance, the result of that function can be computed in advance too.

Instead of doing:

text
for every request:
    render(product)

we do:

text
during build:
    render(product)
    save result

If we have 10,000 products:

text
/products/1.html
/products/2.html
/products/3.html
...

a set of finished artifacts appears.

The user's request no longer starts a renderer. It receives a previously prepared result.

A commercial example: documentation

Technical documentation is almost the perfect case for SSG.

The content:

text
changes relatively rarely
is public
should be available quickly worldwide
usually does not depend on a specific user

After every merge you can run:

text
Markdown
  |
  v
build
  |
  v
HTML
  |
  v
CDN

The user then receives a finished document.

That is why documentation systems, blogs, landing pages or marketing sites fit static generation very well.

Frameworks such as Astro, Next.js or Nuxt can use this model, but the idea itself is much older than modern meta-frameworks.

Why this is so fast

The request-time path becomes very short:

text
browser
  |
  v
CDN
  |
  v
cached HTML

There is no:

text
database query
application startup
component rendering
template engine
server-side data fetching

More importantly, finished HTML can be copied to many geographic locations.

So we do not have to ask a central origin for every page.

That gives a very good profile:

text
low latency
+
high cacheability
+
almost zero render CPU per request

It sounds ideal.

The problem starts when we look at the cost of the build.

The rendering did not disappear

SSG does not eliminate work.

It moves it in time.

Assume:

text
100 pages
x
50 ms render
=
5 seconds

No problem.

Now:

text
100 000 pages
x
50 ms
=
5 000 seconds

That is over an hour of pure rendering with a naive sequential run.

Real systems of course parallelise the work, cache results and run incremental builds, but the fundamental problem remains:

the cost grows with the number of materialised variants.

A curiosity

You can treat SSG like a materialized view from databases.

We have a function:

text
page = f(content, configuration, code)

Instead of computing f() on every read, we store its result.

That gives a very fast read, but creates the classic problem of systems with precomputed data:

when does the result stop being current?

Exactly the same trade-off shows up in caches, indexes or materialized views:

text
cheaper reads
<->
more expensive updates

So SSG is less of a "frontend technique" and more a specific case of precomputation.

Freshness becomes a problem

Assume we generated:

html
<p>Price: 129 zł</p>

At 12:00 the price changes to:

text
119 zł

The static artifact will not change on its own.

Something has to:

text
data change
  |
  v
trigger build
  |
  v
render page again
  |
  v
deploy new artifact
  |
  v
invalidate / replace cache

For documentation this is usually acceptable.

For a product price, availability or a match score, much less so.

So SSG works best when the tolerance for stale content matches the rebuild frequency.

The combinatorics problem

It gets even more interesting with pages generated from many parameters.

Assume a marketplace:

text
category
x
country
x
language
x
page
x
filters

If we try to materialise every combination:

text
100 categories
x 20 countries
x 10 languages
x 100 pages
=
2 000 000 variants

And that is before filters.

Not every possible page will ever be visited.

So generating everything during the build means doing a large amount of work for hypothetical requests.

SSG changes the economics of a request

In SSR we pay for the render when the user arrives:

text
request -> compute

In SSG we pay earlier:

text
build -> compute -> store

The later request is very cheap.

So you can look at the choice between SSR and SSG as a decision about:

when do we want to pay the rendering cost?

text
before the traffic
or during the traffic?

If a page will be visited a million times, one earlier render can be a great investment.

If a page is never visited, the same render was completely pointless.

And this is exactly where SSG hits its natural limit.

We do not want to render everything on every request.

But we also do not want to generate, during the build, millions of pages that nobody may ever open.

We need a third option:

generate the page only when somebody actually needs it, and then keep the result.

That is how we arrive at deferred generation.