Astro islands and the cost of JavaScript
The median page shipped 632 KB of JavaScript to mobile in 2025. Islands architecture makes that number a decision rather than a default, but only if someone measures the bundle after every release.
Contents
Islands architecture renders a page as static HTML and hydrates only the components you mark as interactive. In Astro that mark is a client:* directive, and the default is zero JavaScript. The payload becomes a per-component decision rather than a framework-wide one, which is where the savings come from.
Key takeaways
- The median mobile home page shipped 632 KB of JavaScript in 2025, against 911 KB of images and a total of 2.56 MB, per the HTTP Archive Web Almanac.
- Astro components render to HTML with no client-side runtime. JavaScript ships only for components carrying a
client:*directive. - The directive is the decision:
client:load,client:idle,client:visible,client:mediaandclient:onlyhydrate at different moments and cost different amounts of main thread. - 77% of mobile origins had good INP in 2025 against 97% on desktop, and lab Total Blocking Time rose 58% year over year.
- Islands reduce shipped bytes. They do not reduce the work inside an island, and a single oversized island can still fail INP.
What the median page shipped in 2025
The 2025 Web Almanac, published on 15 January 2026, measured the median home page at 2.86 MB on desktop and 2.56 MB on mobile. JavaScript was the second-largest resource type after images on both.
The HTTP Archive Web Almanac reported that the median mobile home page in 2025 used 632 KB of JavaScript, 911 KB of images and 122 KB of fonts. The desktop median was 697 KB of JavaScript and 1,058 KB of images (HTTP Archive, January 2026).
Two things follow. JavaScript is the resource type most under a development team's control, because a build step compresses images and cannot compress a component library. And a median mobile device parses and executes those 632 KB before the page responds to a tap.
What islands actually change
An Astro component renders to static HTML and ships no client-side runtime. A React, Vue or Svelte component inside an Astro page also renders to markup, and its JavaScript is stripped unless a directive asks for hydration.
That inverts the usual default. In a single-page application the whole tree hydrates and you opt out through code splitting. With islands, nothing hydrates and you opt in per component. The same decision gets made either way, but the safe answer is now the cheap one. It is the argument behind choosing a stack by constraint rather than habit: the framework should make the desired outcome the path of least effort.
Picking a directive is a measurement decision
Astro documents five client directives. client:load hydrates immediately and client:idle waits for the browser to go idle. client:visible waits until the component enters the viewport, client:media hydrates on a media query match, and client:only skips server rendering.
Default to `client:visible` and justify anything more eager. A header search toggle above the fold is a reasonable client:load. A footer accordion, a map, a carousel and a comment widget are all client:visible, and the difference is real work removed from the initial main thread window.
client:only deserves suspicion. It ships the component and renders nothing on the server, so its content is invisible to crawlers and to anyone before hydration completes. Use it for widgets that cannot render without browser APIs, not to silence a hydration mismatch.
What to leave static
Most of a marketing site is text, images and links, and none of that needs hydration. Navigation can be HTML and CSS. Accordions can be <details>. Form validation can start with the constraint validation API and add JavaScript only for what HTML cannot express.
Native elements also arrive with keyboard behavior and announced state already correct, which removes a class of defects that the European Accessibility Act makes expensive to leave in place.
Server islands cover the personalized parts
The usual objection to static output is personalization: a cart count, a logged-in name, a price by region. Astro 5.0, released on 3 December 2024, made server islands stable. A component marked server:defer renders a fallback inside the cached page, then fetches its own content on demand.
The page stays cacheable and one slow island does not block the rest. It is a rendering strategy rather than a hydration strategy, and it removes the most common reason teams abandon static output halfway through a build.
Measuring what you actually ship
Islands make it easy to believe the bundle is small. Measure it anyway, every release.
Track four numbers per template: JavaScript transferred, hydrated islands, Total Blocking Time in the lab, and field INP at the 75th percentile. INP counts an interaction as good at or below 200 ms, across input delay, processing duration and presentation delay.
The 2025 Web Almanac found 77% of mobile origins achieving good INP against 97% on desktop. Lab Total Blocking Time rose 58% compared with 2024: heavier execution, unevenly distributed by device class (HTTP Archive, January 2026).
A budget only works if it fails a build. Set a per-route ceiling in kilobytes, wire it into CI, and treat a breach like a failing test rather than a note in the retro.
FAQ
Does Astro make a site fast automatically?
No. It removes JavaScript you did not ask for and leaves the rest to you. A page with six eager islands, an unoptimized hero and a tag manager loading three vendors will be slow in any framework. The defaults help; the measurements decide.
How many islands is too many?
There is no fixed number, because cost depends on what each island does. The practical test is Total Blocking Time in the lab and INP in the field. If either moves the wrong way after a release, look at the island you added and what it imports.
Do islands help LCP?
Indirectly. LCP is usually decided by the hero image and the critical request chain, not by component hydration, so the fixes described in what actually moves LCP still apply. Islands mostly help responsiveness metrics by keeping the main thread free.
Does this approach survive a redesign?
Yes, if the content model is separate from the components. The hydration decision lives in the template; the content lives in the CMS. That is the same discipline as modelling content so it survives a redesign and keeping design tokens as the contract between design and code.
Start by listing every component that ships JavaScript and asking what breaks without it. Convert the honest answers to static markup, move the rest to client:visible, and reserve eager hydration for the first seconds of interaction. Then set a per-route budget and let it fail builds. In six months the useful question is whether teams held that budget after the third feature request. That, not the framework, decides the number a user downloads.



