Replacing Heavy Dependencies in Island Code
Island modules are rarely large because of the component; they are large because of four dependency families that arrive with it. Removing them is the highest-return payload work available, and most of it is a one-line change. This walkthrough works through the four, extending the techniques in reducing JavaScript payload in islands.
Prerequisites
Implementation Steps
Step 1 — Dates: format where the data is
// Before: 28 KB of date library inside the island, to render one label.
import { formatDistanceToNow } from 'date-fns';
export function Posted({ iso }) { return <time>{formatDistanceToNow(new Date(iso))}</time>; }
// After: the server formats; the island renders a string it was given.
export function Posted({ iso, label }) { return <time dateTime={iso}>{label}</time>; }
// Where a visitor-local rendering is genuinely required, the platform does it:
const local = new Intl.DateTimeFormat(navigator.language, { dateStyle: 'medium' })
.format(new Date(iso)); // 0 KB, built in
This swap has a second benefit beyond size: it removes a common source of hydration mismatch, as described in fixing text content did not match warnings.
Step 2 — Icons: inline what you use
An icon package imported for four icons ships hundreds. Inline the four as SVG in the component or the server template; they compress well and cost nothing to parse.
Step 3 — Validation: keep the schema on the server
Client-side validation improves the experience and cannot be trusted, so the server validates regardless. That makes the client copy a duplicate rather than a necessity. Use the platform’s constraint validation for the immediate feedback — required, type, pattern, minlength — and let the server return field-level messages for everything structural.
Step 4 — Barrels: import the module, not the index
// Before: pulls the whole utils index, which re-exports a chart adapter.
import { formatCount } from '@/lib/utils';
// After: only this module and its own imports.
import { formatCount } from '@/lib/utils/format-count';
Add a lint rule forbidding barrel imports inside island directories. It is the cheapest permanent fix in this list and it prevents the regression rather than detecting it.
Verification
Verify each swap independently rather than all at once, so a regression is attributable. After each change, rebuild the island as a standalone entry point and compare the compressed size against the previous number. Keep the two numbers in the commit message; a reviewer can then see that a claimed twenty-kilobyte saving actually happened.
Then verify behaviour, because these swaps change where work happens. Server-side formatting must produce the same string the library did, including for edge cases such as zero, negative values and the boundary between “yesterday” and a date. Platform validation must reject the same inputs the schema did, or the server will start receiving submissions the client used to block. A short table of inputs and expected outputs, run against both implementations during the migration, catches the differences that matter.
Keeping the Gains
Payload work decays. The dependency you removed returns in a different form, usually through a component that arrived from another part of the codebase where nothing constrained it.
Three mechanisms hold the line, in increasing order of effectiveness. A documented convention helps for a quarter. A lint rule against barrel imports and specific package names inside island directories helps for longer, because it fails at the point of writing rather than at review. A per-module size budget enforced in the build is the only one that survives indefinitely, because it catches every route to the same outcome — a new library, a new import style, an accidental re-export — with one check.
The budget file also becomes the record of intent. When a module legitimately needs to grow, raising its budget is a visible commit that someone approves, which is exactly the conversation that should happen. The same mechanism applied to serialised state is described in reducing Qwik serialization payload size; the two together cover the whole surface of what an island ships.
A last word on what not to replace. Not every large dependency is a mistake: a rich text editor, a charting engine, a map renderer or a video player is large because the problem is large, and reimplementing one to save bytes is a project rather than an optimisation. For those, the correct move is scheduling rather than substitution — defer the island until the visitor asks for it, load it on interaction rather than on viewport, and make sure the server-rendered fallback conveys the content even when the interactive version never loads. A twelve-kilobyte replacement for a two-hundred-kilobyte editor is not a smaller editor; it is a worse one that you now maintain.
Troubleshooting
Is a smaller library always better than a platform API?
The platform API is usually better because it costs nothing to ship, but it is not always sufficient — a date library that handles time zone arithmetic across historical rules does something Intl formatting does not. The right question is what the island actually uses. Most island code formats a value for display, which the platform does completely, and a minority does real calendar arithmetic, which justifies a dependency.
How do I stop a removed dependency from coming back?
Add a size budget per island module to the build and fail the job when it is exceeded. A comment in the file is ignored within a quarter; a failing build is not. The budget also catches the subtler version of the regression, where a different library with the same weight arrives to do the same job under a new name.
What if the dependency is needed on the server too?
Then keep it on the server, where its size costs nothing beyond deploy time, and ship the result rather than the tool. A currency formatted on the server is a string; a schema validated on the server is a boolean plus messages. The island receives the output and stays small, which is the same rule that prevents whole classes of hydration mismatch.
Related
- How to Analyze Island Bundle Size with Rollup Visualizer — finding the dependency worth replacing.
- Reducing JavaScript Payload in Islands Apps — the remove, replace, defer and compress hierarchy.
- Estimating the Cost of a Hydration Boundary — pricing what remains after the replacements.
← Back to Reducing JavaScript Payload in Islands