Estimating the Cost of a Hydration Boundary
βMake it an islandβ sounds free and is not: every boundary has a fixed cost that exists before any of its own code runs. Knowing that number is what lets you decide between four small islands and one merged one. This walkthrough prices a boundary properly, extending the threshold work in when to use islands versus full hydration.
Prerequisites
Implementation Steps
Step 1 β Measure the module in isolation
Build the component as its own entry point and read the output size. Doing this before the boundary exists avoids the usual mistake of measuring after integration, when shared chunks make the marginal cost impossible to see.
# Rollup or esbuild, one entry, production settings, no shared chunks.
npx esbuild src/components/Typeahead.tsx --bundle --minify --format=esm --outfile=/tmp/typeahead.js && gzip -c /tmp/typeahead.js | wc -c
# β 11208 β the marginal payload, compressed
Step 2 β Measure the props payload from a real page
// In the console on a page rendering the candidate: how many bytes do the
// props add to the document, for every visitor, whether or not this activates?
const el = document.querySelector('[data-island="typeahead"]');
const props = el.querySelector(':scope > script[type="application/json"]');
console.log(new Blob([props.textContent]).size, 'bytes of props');
// β 4820 bytes β paid by 100% of visitors; the module is paid by maybe 12%
That ratio is the crux. A boundary whose props are five kilobytes and whose module is eleven costs more in aggregate through the props than through the code, once you weight by how many visitors pay each.
Step 3 β Compare against the merged alternative
separate: save-button 1.6 KB module + 0.2 KB props + 1.5 KB overhead
quantity 1.2 KB module + 0.1 KB props + 1.5 KB overhead
compare-box 0.9 KB module + 0.1 KB props + 1.5 KB overhead
total 3.7 KB modules + 0.4 KB props + 4.5 KB overhead = 8.6 KB
merged: row-controls 3.9 KB module + 0.4 KB props + 1.5 KB overhead = 5.8 KB
Three tiny islands that always activate together cost nearly half again as much as one merged island, entirely in overhead. This is the fragmentation failure described in understanding partial hydration, expressed as a number rather than a warning.
Step 4 β Record the estimate where it will be checked
Put the three numbers in the island manifest beside the boundary. When the module doubles two quarters later, the diff shows a number changing rather than a file changing, and someone asks why.
Verification
Validate the estimate after implementation with two measurements. Compare the routeβs transferred document size before and after adding the boundary β the delta should match your props estimate closely, and a large discrepancy means more was serialised than intended. Then compare transferred JavaScript with the island triggered and untriggered; the difference is the module cost, and it should match the isolated build within a few per cent.
If either number is far from the estimate, the cause is almost always a shared dependency being counted differently. An island that is the first to import a library carries its full weight; the second island to import it appears free. That accounting artefact is why estimates should be made against a clean baseline and why the manifest should record which islands share which large dependencies.
Using the Numbers to Decide
Three decisions follow directly from a priced boundary.
Split or merge. If two candidates activate together and their combined module is smaller than the overhead of keeping them apart, merge. The threshold is lower than intuition suggests: at roughly one and a half kilobytes of overhead per boundary, anything under about three kilobytes of module code is mostly overhead.
Defer or not. A boundary whose props are large and whose module is small is a candidate for moving data out of props and into a fetch on activation β the props are paid by everyone, the fetch only by those who interact. A boundary with the opposite shape is a candidate for a later trigger, since the payload only lands when it is needed.
Island or no island. If the priced boundary is larger than the value of the interaction it enables, the honest answer may be a link. That comparison is uncomfortable to make in the abstract and easy once there is a number: a five-kilobyte island for a control used by two per cent of visitors is a poor trade, and the audit in auditing which components need hydration exists to surface exactly those.
Troubleshooting
What exactly is the fixed overhead of a boundary?
Four things: the marker markup and its props script in the document, an entry in whatever registry the loader keeps, one network request for the module, and one mount task on the main thread. On a warm cache the request is nearly free and the mount task dominates; on a cold one the request dominates. As a planning figure, treat a boundary as costing a couple of kilobytes and a few milliseconds before its own code is counted.
Do props really matter if the island never activates?
Yes, and this is the cost teams forget. Serialised props are bytes in the HTML, paid by every visitor on every request, including the majority who never scroll to the island. A boundary with a large props payload can cost more in aggregate than its module does, because the module is fetched by a minority and the props are downloaded by everyone.
When is merging two islands the right call?
When they always activate at the same moment, sit adjacent in the layout and share a runtime. Then merging removes one overhead and one round trip at no behavioural cost. Keep them separate when their triggers differ, when one is far larger than the other, or when they belong to different teams β the coordination cost of a shared boundary can outweigh a couple of kilobytes.
Related
- Auditing Which Components Need Hydration β producing the candidate list this prices.
- When to Use Islands vs Full Hydration β the thresholds these numbers feed.
- Reducing JavaScript Payload in Islands Apps β shrinking the marginal cost once the boundary exists.
β Back to When to Use Islands vs Full Hydration