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:
request
|
v
render
|
v
HTML
SSG moves the same render to build time:
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:
HTML = render(data)
If data is known in advance, the result of that function can be computed in advance too.
Instead of doing:
for every request:
render(product)
we do:
during build:
render(product)
save result
If we have 10,000 products:
/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:
changes relatively rarely
is public
should be available quickly worldwide
usually does not depend on a specific user
After every merge you can run:
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:
browser
|
v
CDN
|
v
cached HTML
There is no:
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:
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:
100 pages
x
50 ms render
=
5 seconds
No problem.
Now:
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:
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:
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:
<p>Price: 129 zł</p>
At 12:00 the price changes to:
119 zł
The static artifact will not change on its own.
Something has to:
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:
category
x
country
x
language
x
page
x
filters
If we try to materialise every combination:
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:
request -> compute
In SSG we pay earlier:
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?
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.