There is a category of confusion in web development that survives for years because everyone involved is being reasonable, and the confusion around incremental static regeneration is one of those. The term describes something real, the implementations are well engineered, the documentation is honest about the mechanics if you read it carefully enough, and yet the phrase itself has drifted far enough from the thing it names that two developers can use it in a meeting and mean architectures that behave nothing alike in production.
Underneath the terminology there is a question about what a web page is supposed to be, and my answer has not changed much since I started doing this work, which is that a page is closer to a printed edition than to a service call, and that the difference between those two ideas is exactly the difference the vocabulary keeps blurring.
I want to set out how we classify these strategies in Reeweb, why we chose the boundary we chose, and where the common counter-argument to our position is actually correct, because it is correct in a way that is worth stating plainly rather than arguing past. Because the whole argument turns on what the words mean, the definitions come first, and I have tried to state the established meanings as their own documentation states them rather than as a version convenient to my case. The reason to be this careful is that a term is a promise about behavior, and the work of architecture is largely the work of meeting expectations, both the ones a client forms when we tell them a page is static and the ones a developer forms when they inherit the system three years later, so a name that quietly covers two different behaviors is not a vocabulary problem but a future disappointment with a delivery date.
The Terms, As Everyone Uses Them
Static site generation is rendering every page into a finished file during a build, before anything is deployed, so that serving a request is a file read and nothing more.
Server-side rendering is assembling the response when the request arrives, which is what you need when the output depends on who is asking or on data that cannot be known in advance.
Stale-while-revalidate is a caching pattern, older than any of the frameworks discussing it here, where a cached response past its freshness lifetime is served immediately to the requester while a fresh copy is fetched in the background, so the requester trades absolute freshness for latency.
Incremental static regeneration is the feature name for applying that pattern to page rendering. Its own documentation is explicit that it is a caching strategy that combines the speed of static content with the flexibility of server-side rendering, following the stale-while-revalidate model. In practice it covers several behaviors under one name: routes pre-rendered during the build and refreshed on a timer, routes not pre-rendered at all and generated the first time somebody requests them, and cached output invalidated explicitly by an API call rather than by a clock.
On-demand revalidation is that explicit invalidation, where publishing content calls into the platform to mark a path stale, and the docs are clear that this marks the entry rather than regenerating it, since regeneration happens on the next request to that path.
Deferred static generation is the name some tools use for the sub-case where a page is deliberately excluded from the build and rendered the first time it is requested, and I mention it because it is the behavior my argument is really about, named honestly.
Those are the shared definitions and I have no quarrel with any of them. What follows is a different axis for sorting the same things, which does not replace the vocabulary above but answers a question the vocabulary does not.
The Question That Sorts Everything
The classification we use has one question in it, and the question is this: did the page exist as a finished output before the visitor asked for it? Everything a visitor notices about a page follows from the answer, which is why we prefer it to any sorting based on what a feature is called.
Sorted by that question, the strategies fall out cleanly. Static generation renders everything before publication, so every page exists as a file before anyone requests it. Incremental static generation renders a selected subset before publication, which is the same model applied to a build that would otherwise take too long, and the selection is the only new idea. Signal-driven regeneration is where a content change triggers selected outputs to regenerate and then publish, so the rendering is still finished before the visitor arrives, but the trigger moved from a manual build to an event in the content system. Server rendering happens at request time, when the visitor asks and the server assembles a response for that specific request.
And then there is the case that started this note, which is request-time rendering with stale output, background revalidation, and persisted results, where the framework holds a previously rendered response and decides on each request whether to serve it, serve it while refreshing behind the scenes, or render fresh because nothing usable is held. That decision belongs to the request, and a system whose freshness is determined by what happens when a request arrives is server rendering with a cache in front of it, whatever the feature is called.
The distinction I am drawing is narrower than the feature name, and this matters because the feature is not one thing. A route pre-rendered during the build and refreshed on a timer sits on the generation side of the boundary for as long as its pre-rendered output lasts, while the deferred case, where the page is deliberately left out of the build and rendered when somebody first asks for it, sits on the other side from the beginning. When I say on-request regeneration is server rendering with a cache, I mean the deferred behavior and the moment a timer expires, not the feature as a whole, and the reason the sub-case needs its own name is exactly that the umbrella term hides which of the two you have configured.
The decisive boundary is not how the output is stored but whether rendering was finished before the request arrived.
The two shapes are easy to hold side by side. In the first, a content change causes a render, the render causes a publish, and only afterwards does a visitor read something that already exists. In the second, a visitor request causes a cache decision, and the cache decision causes either a render or a revalidation. Both can be fast, both are legitimate, and they are not the same architecture.
The Counter-Argument, Which Is Not Wrong
The standard response to this is that we are conflating rendering with caching, and that incremental regeneration was never presented as a rendering strategy in the first place but as a caching layer that extends static generation with selective rebuilding. Anyone who has read the platform documentation carefully will point out that it says exactly this, that it describes a stale-while-revalidate model, that on-demand invalidation exists precisely so a content change can push freshness rather than waiting for a timer, and that the whole point is to avoid rendering on every request the way naive server rendering would.
All of that is accurate and I do not want to pretend otherwise, because the engineering behind these systems is serious and the people who built them understood the tradeoffs they were making better than most of the commentary about them does. If the argument were only about whether a term has been used precisely, we would have nothing worth writing about, since terminology drift is the least interesting problem in our field and correcting it is mostly a way of feeling clever.
There is a stronger version of the objection too, which is that my clean boundary is really a spectrum, since a route that is pre-rendered at build time and revalidated once a day spends almost all of its life behaving exactly like a published file, and calling that server rendering because of what happens in the one second after the timer expires is pedantry rather than analysis. That objection is correct, and it is why the paragraph above narrows the claim to the deferred case and the expiry moment rather than to the feature as a whole.
The reason we still draw the line where we draw it is that we are not classifying these strategies from the position of the person who maintains the platform, where "caching layer over static generation" is an accurate and useful description of the code that was written. We classify them from the position of the person waiting for the page, and from that seat the useful question is not what the layer is made of but whether there exists any request that can arrive and find no finished answer waiting, because that possibility is what you have to reason about, monitor, and pay for, and its absence is what makes the other model simple to talk about.
The Newspaper Was Already Printed
The mental model I keep returning to is the newspaper, or the book, because those are the publishing technologies that solved this problem long before any of us were arguing about it. A newspaper is an edition, which means the presses ran during the night, the copies exist as physical objects before any reader wakes up, and the reader who buys one at the station carries it onto the train where it keeps working with no further involvement from anyone who made it. The publisher is not consulted when you turn to page seven. There is no arrangement under which the paper is blank until you look at it and the print shop is telephoned to set that page while you wait.
What the presses give you is not speed but independence, and the reason the industry organized itself around nightly editions is that this independence is what makes the thing durable, since an edition that exists can be carried, kept, archived, quoted, and read again in ten years, while a page that only exists when somebody asks for it is not an object at all but a service, and services have opening hours whether or not their operators describe them that way. The newspaper also handles freshness in the honest way, because when the news changes the presses run again and produce a new edition, and nobody experiences this as a limitation, since the alternative of a paper that rewrites itself in your hands would be unsettling rather than convenient.
A web page can be an edition, or it can be a request for service that usually gets answered quickly. Both are legitimate. They are not the same promise.
I do not want to push the analogy past where it holds, because the web genuinely does things the printed page cannot, and a personalized dashboard or an authenticated account view is not a newspaper and should never be built as one, which is precisely why server rendering exists and is the correct answer for that work. But for the things that actually are publications, which is to say articles, documentation, product pages, marketing sites, and the long tail of content a business puts on the web so that people can find and read it, the edition model is the one that matches what the thing actually is, and the reason we default to it is that most of what we are asked to build turns out on inspection to be a publication rather than an application.
What the Visitor Actually Experiences
At Reepolee we build for the visitor's experience rather than for the elegance of the delivery mechanism, and this is where the two models genuinely diverge, because they fail differently and they fail at different moments. Choosing between them is one of those decisions where the convenient option and the responsible option point in different directions, since the architecture that is most impressive to describe is not automatically the one that serves the person reading the page, and the honest question is never which approach we would enjoy building but which one the problem actually asked for.
When rendering finished before publication, the worst case for a visitor is bounded by something a human decided, which is that the content is as fresh as the last publish, and no visitor can arrive at a moment where the page does not exist yet. There is no first-visitor penalty and no cold path, the person who arrives one second after a release and the person who arrives an hour later are served the same finished file, and no page is slow because of when somebody happened to click a link. The failure mode is staleness, which is visible, explainable to the person who publishes content, and fixable by publishing again. This is not a claim of superiority, since the same property is trivially available to anyone who renders their whole site ahead of time and is only interesting because it is cheap for us and expensive at a size where full rendering stops being practical.
When the decision happens at request time, the visitor inherits a set of conditions they did not choose and cannot see, and here I have to be careful, because the mature managed platforms have spent years closing exactly these gaps and it would be dishonest to describe the naive version as if it were the current state of the art. A well operated setup pre-renders popular paths at build time, holds the cache in durable storage rather than in a process that can restart, collapses concurrent requests to the same uncached path into one render, shields a regional miss by reading from a durable cache before invoking the function, and propagates a purge to every region within a few hundred milliseconds, which together remove most of the failure modes that people were complaining about in the early years.
So is there anything left to complain about, once a platform has done all of that well? Yes, but less than the usual criticism claims, and the remainder is worth stating precisely rather than loudly. Whenever a path has not been pre-rendered, someone has to be the request that causes the first render, and on time-based revalidation the visitor arriving after the interval expires receives content the system already knows is stale, which is the deliberate design and not a defect but is still a different experience than the one the next visitor gets. A regional miss that falls through to durable storage is faster than a cold render but slower than a local hit, so the geography of who is reading still shows up in the numbers. The cache is scoped per deployment on at least one major platform, which means a new deployment starts with its own cache rather than inheriting the previous one, and warmth has to be rebuilt by traffic. That last point is the one that matters most for the projects we build, because the traffic patterns that keep pages warm are precisely the patterns a small site does not have, so the pages most likely to be served cold are the long-tail pages a small business most wants to be findable.
None of this makes the model bad, and for a catalogue with a hundred thousand products that change hourly it is clearly the correct engineering, because rendering all of it ahead of time is not a real option and the traffic concentrates on pages that stay warm anyway. The honest summary is that request-time regeneration converts a build-time cost into a distribution of request-time outcomes, where the distribution is tight when the engineering is good and the traffic is dense, and gets wider as the site gets smaller and quieter. What I find worth saying plainly is that the number usually quoted for these systems is the warm hit, which is both the best case and the common case, while the visitor's impression is formed by the tail, and a small site has proportionally more tail than the benchmark suggests.
What This Means in Reeweb
Reeweb renders before publication, and the whole pipeline is arranged around that being true rather than mostly true. The generator walks the source tree, renders every .ree template and every Markdown file, expands paginated routes, writes the finished HTML into dist/, and what gets deployed is that directory, so there is no code path in which a visitor's request causes a render, because by the time anything is serving traffic the rendering has already happened and the server's only job is to hand over a file.
We call this generation rather than a build, and the distinction is not vanity, since nothing here is transpiled, minified into unreadability, or packed into artifacts that need a tool to interpret them later. The generator reads templates and content and writes HTML, which means the output is the same kind of thing as the input, only finished, and a person can open any file in dist/ and read it without knowing anything about the program that produced it. The one concession is that a page's local scripts are concatenated into a single file so that a page makes two script requests instead of nine, but concatenation is not compilation, the result is the same JavaScript in the same order, and a browser or a reader can still make sense of it. A build implies assembly of parts into something whose relationship to its sources needs explaining, while generation implies what the newspaper does, which is that the words were set and then printed.
Today the generator runs over everything each time, since a site of a few hundred pages renders in seconds and there has been no pressure to make it cleverer. The selective half of the model, where a content change regenerates only the pages that actually depend on it, is designed and written down but not yet built, and I would rather say that plainly than let a blog post imply a finished feature, because the design work is what forced the vocabulary question, since you cannot plan an incremental generator without deciding what you mean by incremental. That design problem, which turns out to be harder than the rendering it saves, is a note of its own. Our planning documents settled on the same boundary this note argues for, which is that incremental generation and request-time regeneration solve different problems, and a site can publish static artifacts incrementally within seconds without ever rendering on request.
The signal-driven half of the model lives in the publisher, which watches the repository and treats a change as the trigger:
signal(): void {
this.schedule_dev_reload();
this.request_render(QUIET_PERIOD_MS);
}
That quiet period is there because content changes arrive in bursts, so a two second delay collapses a series of edits into one render instead of starting a generation pass for each keystroke, and the render that eventually runs produces a full candidate that is diffed against what is currently deployed. The person publishing then sees what changed, and deployment is a separate deliberate step which only proceeds when the working tree is clean and the branch is the publish branch:
private release_allowed(): boolean {
return !this.git_state.error
&& this.git_state.clean
&& this.git_state.branch === this.config.publish_branch;
}
The sequence, then, is that a content change causes a render, the render produces a reviewable candidate, a person publishes it, and only afterwards does a visitor read the result, which is the first of the two shapes described above and the reason we are comfortable calling it regeneration rather than caching. That candidate is the edition, the diff is the proof sheet somebody looks at before it goes out, and the deploy is the moment the papers leave the building, which is not an analogy we imposed on the design so much as the shape the design took once we decided that publishing should be a deliberate act. Nothing about a visitor's request participates in the decision, and the practical consequence is that we can answer questions about performance without qualifying the answer, because there is no warm case and cold case to distinguish between.
Where a page genuinely cannot be known ahead of time, because it depends on who is asking or on data that changes between one request and the next, we render it on request and call that server rendering, and if we put a cache in front of it we call it a cache. The honesty of the naming is not for its own sake but because it keeps the conversation about the visitor: when someone asks how fast a page is, the answer for a published file is one number, and the answer for a cached render is a distribution with a tail that somebody is going to land in.
The reason we care about keeping those two answers separate is that the choice between them should follow from what a project actually needs rather than from which term sounds more modern, and the properties that genuinely call for request-time regeneration are specific and recognizable, since they are personalization, authentication, content decisions that can only be made when the request arrives, and data whose volume makes rendering it all ahead of time impossible rather than merely inconvenient. When a project has those properties, stale-while-revalidate is the right tool and we would reach for it without embarrassment. When it does not, adopting the machinery anyway means accepting a cold path, a first-visitor penalty, and a cache hit ratio to worry about, in exchange for solving a problem the project did not have.
Using the Definition
So this is the vocabulary we will use for Reeweb from here on, and it is deliberately narrow. Rendering that finishes before publication is generation, whether it covers the whole site, a selected subset, or a set of outputs triggered by a content signal. Rendering that happens when a request arrives is server rendering, and storing its results, serving them stale, or refreshing them in the background makes it server rendering with a cache, which is a good technique that we use where it fits and do not describe as anything else.
None of that replaces the definitions at the top, which remain the words everyone else uses and which we will keep using when we talk about somebody else's system, because a vocabulary that only works inside one company is not a vocabulary but a dialect. What we are adding is one question laid across the existing terms, which is whether a request can arrive and find no finished answer, and the value of asking it is that the answer is a single yes or no while the feature names are not.
I am not proposing this as a correction to anyone, since the platforms that use the other terminology have their own reasons, their documentation describes the mechanics accurately for anyone who reads it, and their users are not confused about what their systems do. It is how we intend to describe our own, because the projects we build are small enough that rendering everything ahead of time is not a sacrifice but simply what the machine does in a few seconds while somebody reads the diff, and when that is your situation, the model with no cold path and no first-visitor penalty is not a compromise you are settling for but the plain shape of the thing.
What I want from a page we publish is what I want from a printed edition, which is that it exists before anyone asks for it, that it is the same for the reader in the next town as for the one downstairs, and that nobody has to be reachable at two in the morning for it to keep working. The presses run when the content changes, the edition goes out, and after that the page belongs to whoever is reading it.