Resumability: what if we don't hydrate at all?
- date
- category
- Frontend
- reading
- 4 min / 742 words
Hydration assumes the browser receives HTML rendered on the server and then reconstructs the client-side runtime needed for the application to keep working.
Even if we limit hydration to individual islands, we still often run a similar cycle:
server
|
v
render component
|
v
HTML
browser
|
v
load JS
|
v
recreate component state
|
v
attach behavior
Resumability tries to change the very nature of that process.
Instead of reconstructing the application on the client, we try to resume execution where the server left it.
Hydration reconstructs, resumability continues
The simplest comparison:
hydration:
server executes
|
v
HTML
|
v
browser executes again
|
v
runtime reconstructed
Resumability:
server executes
|
v
HTML + serialized execution metadata
|
v
browser
|
v
continue when needed
This does not mean literally freezing the CPU on the server and shipping stack frames across the network.
It means recording enough information that the client does not have to eagerly rebuild the whole application structure.
The problem with classic hydration
Assume a page with 200 interactive components.
During the entire session the user may click three of them.
Classic hydration can still do the work needed to activate all 200.
200 components
|
v
download code
|
v
initialize runtime
|
v
reconstruct state
|
v
attach behavior
That is classic eager execution.
We pay the cost now because we assume it may be needed later.
Resumability tries to invert the model:
component exists
|
v
no interaction
|
v
no execution
user clicks
|
v
load exact code needed
|
v
resume
A commercial example: a large e-commerce portal
Imagine a product page containing:
a gallery
a wishlist
a variant selector
tooltips
recommendations
an FAQ accordion
reviews
a delivery calculator
a newsletter
recently viewed
Most users will not use all of those features.
If we activate the whole runtime during page load, the client pays for potential interactivity.
A resumable model can try to move the cost closer to the moment of real use.
page load
|
v
mostly HTML
user opens delivery calculator
|
v
load calculator logic
|
v
continue interaction
That is a more aggressive form of lazy execution than plain lazy loading of a component.
HTML alone is not enough
Here we return to a fundamental problem.
Assume:
function Product({ id }) {
const add = () => addToCart(id);
return <button onClick={add}>Add to cart</button>;
}
The server can produce:
<button>Add to cart</button>
But to resume the interaction the browser has to know:
this button
|
v
needs handler X
|
v
handler requires product id 42
|
v
handler implementation lives in chunk Y
That means the server-rendered output has to carry additional metadata describing the links between the DOM, the state and the code.
So resumability requires a more deliberate transfer format than HTML alone.
A curiosity
The hardest part is not serialising data.
We have been able to do that for a long time:
JSON.stringify({
productId: 42,
quantity: 1
});
The problem is serialising execution dependencies.
A closure:
() => addToCart(product.id)
is not a simple value.
It carries a reference to code and to a lexical environment.
So a resumable framework has to transform the program in a way that makes it possible to find, later:
handler
state
code location
dependencies
without having executed the whole component tree first.
That is why resumability is largely a compiler and runtime problem, not just an SSR technique.
An event can trigger loading the code
In a classic application:
load page
|
v
load component JS
|
v
attach click handler
|
v
wait for click
A resumable runtime can reverse the order:
load page
|
v
wait for click
|
v
identify handler
|
v
load handler code
|
v
execute
That is a very interesting shift.
JavaScript stops being loaded because a component exists.
It is loaded because execution became necessary.
Who captures the first event?
Of course something still has to run on the client.
If the user clicks:
<button>Add to cart</button>
the browser has to know the click matters to the application.
That is why resumability usually needs a small bootstrap runtime.
Its job can be:
event
|
v
inspect metadata
|
v
find required code
|
v
load chunk
|
v
restore required state
|
v
execute handler
So JavaScript does not disappear.
What changes is its granularity and moment of execution.
The cost moves somewhere else
Every optimisation costs something.
Resumability can reduce startup CPU and the amount of eager JavaScript, but it requires:
serialising additional state
metadata in the HTML
code transformation during the build
granular chunks
a runtime that resolves dependencies
So we can reduce:
initial execution cost
at the price of more complexity in:
the compiler
+
the delivery format
+
the runtime
That is a very typical architectural trade-off.
A typical solution: Qwik
The best-known modern example is Qwik.
Its architecture is designed around resumability rather than classic eager hydration.
The framework tries to serialise the information needed for later execution and to load code as granularly as possible, only when it is needed.
More important than the library itself is the idea:
starting an application does not have to mean starting the whole application.
Resumability is not automatically better
For a page with a lot of content and relatively few interactions the model is very attractive.
But imagine an application like Figma.
The user immediately:
selects elements
drags them
changes properties
uses shortcuts
opens panels
modifies shared state
A large part of the application becomes active very quickly.
The cost of lazy resolution may then matter less, and a classic long-lived runtime may be the simpler model.
Again, there is no single best architecture.
The next boundary: code that never reaches the client
Resumability asks:
can we avoid running client code until we really need it?
But you can go even further.
What if some components should never have a client-side implementation at all?
If a component:
fetches data from a database
performs a server-only transformation
renders a static part of the UI
then there may be no reason for its code to reach the browser.
At that point we stop optimising only the moment of execution.
We start splitting the module graph itself into server and client code.
And that is exactly where Server Components come in.