Astro & SvelteKit Performance: Shipping Less, Loading Smarter
This topic is part of Framework Performance. Astro and SvelteKit approach performance from different angles, and both start from strong defaults. Astro renders pages to static HTML (at build time or on the server) and ships zero JavaScript by default. Interactive components — written in React, Vue, Svelte, Solid or others — are added as islands, each hydrated independently according to a client directive (client:load, client:idle, client:visible, client:media, client:only). A content page with one interactive widget ships the JavaScript for that widget only.
SvelteKit compiles Svelte components into small, efficient JavaScript that updates the DOM directly without a virtual DOM, server-renders by default, and provides load functions for data, streaming of promises, and preloading of code and data on link hover or tap. Its runtime is small, and its router makes client-side navigations fast.
Both frameworks can still be slowed down: too many islands with client:load, heavy UI libraries inside islands, server rendering where prerendering would do, data waterfalls in load functions, and images and fonts configured poorly. This topic covers how to use each framework's tools to keep pages fast, and how to choose between them for content-heavy sites.
The Metric Degradation This Topic Addresses
For Astro sites, the main risks are INP and TBT from islands that hydrate eagerly or include heavy dependencies, and LCP from images or fonts that are not optimised. For SvelteKit sites, risks include LCP and TTFB from slow load functions and data waterfalls, INP from large reactive updates, and slow client navigations when preloading is disabled or data is not cached. Both frameworks generally produce good results when their defaults are kept.
Prerequisites
- Build output and bundle analysis (Astro and SvelteKit both use Vite;
rollup-plugin-visualizerworks for both). - A list of islands per page (Astro) or route
loadfunctions and their data sources (SvelteKit). - Field Core Web Vitals per route.
1. Environment Setup: Inventory JavaScript per Page
For Astro, search pages for client: directives and note each island's framework and dependencies. For SvelteKit, check the per-route client chunks in the build output and the data loaded in +page.ts, +page.server.ts and +layout load functions.
2. Capture a Baseline
Record JavaScript per route (compressed), TBT and hydration time in throttled lab traces, TTFB for server-rendered routes, and field LCP and INP.
3. Isolate Causes
Too much JavaScript in Astro → islands with eager directives or large dependencies, or framework runtimes from multiple UI frameworks. Slow TTFB in SvelteKit → sequential awaits in load functions, uncached API calls, server rendering for static content. Slow navigations → preloading disabled, large route chunks, or data dependencies that waterfall.
4. Apply Framework-Native Fixes
In Astro, choose the latest acceptable directive for each island (client:visible, client:idle, client:media), keep island dependencies small, prefer one UI framework, and use astro:assets for images. In SvelteKit, prerender static routes, run independent loads in parallel, stream non-critical data by returning promises, and keep data-sveltekit-preload-data on links.
---
// src/pages/product/[slug].astro
import Gallery from '../../components/Gallery.svelte';
import Reviews from '../../components/Reviews.jsx';
import { Image } from 'astro:assets';
const { product } = Astro.props;
---
<Image src={product.hero} alt={product.name} width={1200} height={900} loading="eager" fetchpriority="high" />
<Gallery client:visible images={product.images} />
<Reviews client:idle productId={product.id} />
<!-- trade-off: client:idle hydrates reviews soon after load even if never scrolled
to; client:visible would save that work for users who do not scroll. -->
Deconstructing Islands and Compiled Hydration
In Astro, the page is HTML. Each island is a placeholder element with props serialised into attributes and a small loader script that, when its directive's condition is met, downloads the component's code and its framework's runtime and hydrates just that element. Islands do not share a component tree; they are independent apps, which keeps hydration cost proportional to interactive content. The cost of an island is its component code plus its framework runtime (shared among islands of the same framework).
In SvelteKit, the whole page is a Svelte component tree that hydrates after server rendering. Svelte's compiler generates code that sets up DOM updates without diffing, and its runtime is small, so hydration is cheap compared with virtual-DOM frameworks of similar complexity. But it is still whole-page hydration: large pages with many components hydrate more. SvelteKit's preloading, code splitting per route and server-side data loading make navigations fast once the app is running.
Advanced Diagnostics and Edge Cases
Mixing UI frameworks in Astro. Islands in React, Vue and Svelte on the same page ship three runtimes; standardise on one where possible.
client:only. Skips server rendering of the island; the space is empty until JavaScript runs, risking CLS and slower content. Use only for components that cannot render on the server.
Large props. Island props are serialised into HTML; pass IDs or minimal data rather than large objects.
SvelteKit load waterfalls. await parent() and sequential awaits in load functions serialise data fetching; parallelise where possible.
Universal vs server load. +page.ts load runs on both server and client; heavy server-only work belongs in +page.server.ts.
Validation and Budgeting
Budget JavaScript per route, count islands with eager directives (Astro), and track TTFB for server-rendered routes (SvelteKit). Validate with lab traces and field LCP and INP per route.
| Budget | Astro target | SvelteKit target |
|---|---|---|
| Client JS per content route | < 50 KB | < 80 KB |
| Eager islands (client:load) | 0-1 | n/a |
| Static routes prerendered | Yes | Yes |
| Load function waterfalls | n/a | None on key routes |
Worked Example: A SaaS Documentation and Marketing Site
A SaaS company rebuilt its marketing and documentation site from a React SPA (280KB JavaScript, LCP p75 3.2 seconds) in Astro. Most pages became static HTML. Interactive parts — a pricing calculator, a search box and a code playground — became React islands: the calculator with client:visible, search with client:idle (opened from the header), and the playground with client:visible on the pages that included it. Documentation pages shipped 12KB of JavaScript (search only), marketing pages up to 45KB. LCP p75 fell to 1.4 seconds and INP p75 to 90ms. The product's logged-in app remained a separate SvelteKit application, linked from the site.
Choosing a Directive in Astro
The directive determines when an island's JavaScript loads and runs. client:load hydrates immediately — use it only for above-the-fold controls needed at once. client:idle hydrates when the browser is idle after load — good for widgets needed soon. client:visible hydrates when the island scrolls into view — the best default for below-the-fold interactivity. client:media hydrates when a media query matches — useful for components only interactive on certain screen sizes. client:only renders only on the client — a last resort. The guide on client directives covers choosing per component.
Preloading in SvelteKit
SvelteKit preloads code and data for links on hover or tap by default in new projects (data-sveltekit-preload-data="hover" on the body). Preloading data on hover gives navigations a head start of 100–300ms; preloading code on viewport (data-sveltekit-preload-code="viewport") makes route chunks ready before users click. Load functions should be fast and cache-friendly so preloading does not create server load for links users do not follow. The SvelteKit preloading guide covers the options and trade-offs.
Images and Fonts in Both Frameworks
Astro's astro:assets (<Image> and <Picture>) generates optimised, responsive images at build time or on demand, with dimensions to prevent CLS; set loading="eager" and fetchpriority="high" on the LCP image. SvelteKit has an enhanced image plugin (@sveltejs/enhanced-img) for build-time optimisation, or you can use an image CDN. For fonts, self-host with font-display: swap and size-adjusted fallbacks; Astro has built-in font support in recent versions, and both work well with Fontsource packages.
Third-Party Scripts on Lightweight Sites
When a page ships almost no first-party JavaScript, third-party scripts become the dominant cost. On many Astro sites, a tag manager or analytics bundle outweighs all islands combined. Load analytics with defer or after the load event, put chat and social widgets behind facades that load on interaction, and consider running compatible scripts in a web worker with Partytown, which has an Astro integration. In SvelteKit, load third-party scripts in onMount or after navigation completes rather than in the document head. The fastest framework cannot compensate for a blocking third-party script in the head. Audit third-party scripts with the same rigour as islands: list each one, measure its main-thread time on a throttled device, and load it as late as its purpose allows.
A Rollout Plan
- Inventory JavaScript per route and, in Astro, islands with their directives.
- Astro: switch islands to the latest acceptable directive; consolidate UI frameworks.
- SvelteKit: prerender static routes; parallelise and stream load functions.
- Configure images (LCP priority, sizes) and self-hosted fonts.
- Keep preloading enabled and load functions cacheable.
- Add JavaScript budgets and field monitoring per route, and review both after every release.
Svelte 5 Runes and Update Performance
Svelte 5 introduced runes — $state, $derived, $effect and $props — which make reactivity explicit and fine-grained. State declared with $state is tracked at the property level, derived values recompute only when their inputs change, and updates touch only the DOM nodes that depend on changed values. For large collections, $state.raw avoids deep proxying (similar to Vue's shallowRef), which helps when data is replaced rather than mutated. Keyed {#each} blocks let Svelte move existing DOM nodes instead of recreating them when lists reorder. These features keep INP low on interactive SvelteKit pages; problems usually come from very large lists without virtualization or effects that do heavy work on every change.
Content Collections and Build Performance
Large content sites built with Astro rely on content collections for Markdown, MDX and CMS data. Build time grows with page count and image processing; for thousands of pages, enable incremental content caching, process images once and cache the results, and consider on-demand rendering with caching for rarely visited pages. SvelteKit sites face similar choices with prerendering: prerender the pages that receive most traffic and render the rest on demand with CDN caching. Fast builds matter for performance indirectly — teams with slow builds tend to defer optimisations and content fixes.
Measuring Astro and SvelteKit Sites
Astro sites are multi-page by default, so standard Core Web Vitals measurement with the web-vitals library covers every navigation. If you enable Astro's client router (view transitions), navigations become soft navigations and need the same care as single-page apps. SvelteKit apps navigate client-side after the first load; measure first loads with Core Web Vitals and client navigations with custom timings (from click to the new page rendering), and segment both by route. In both cases, the Long Animation Frames API helps attribute slow interactions to islands, components or third-party scripts.
Common Pitfalls
- client:load everywhere. Islands hydrate eagerly, losing the benefit.
- Several UI frameworks on one page. Multiple runtimes downloaded.
- Large serialised props. Bloated HTML.
- Sequential awaits in load functions. Slow TTFB and navigations.
- Universal load doing server-only work. Duplicated work on the client.
FAQ
Is Astro faster than Next.js?
For content-heavy sites with little interactivity, Astro typically ships far less JavaScript by default. For highly interactive apps, the difference depends on architecture more than framework.
What is an island in Astro?
An interactive component embedded in static HTML and hydrated independently according to its client directive.
Does SvelteKit use a virtual DOM?
No. Svelte compiles components into code that updates the DOM directly, which keeps the runtime small and updates efficient.
Should I prerender SvelteKit pages?
Yes, for pages whose content is known at build time. Set export const prerender = true on those routes.
Can I use React components in Astro?
Yes, as islands. Each island hydrates independently; React's runtime is shared among React islands on the page.
How does SvelteKit preloading work?
Links with preload attributes fetch the next route's code (and optionally data) on hover, tap or visibility, so navigation starts with much of the work done.
Do Astro view transitions slow pages down?
They add a small client router script when enabled. For most content sites the cost is minor; measure INP and navigation timing after enabling.
Does Astro work with a headless CMS?
Yes. Content can be fetched at build time for static pages or on demand with caching for server-rendered pages, and webhooks can trigger rebuilds or cache purges.
Is SvelteKit suitable for content sites?
Yes. With prerendering and minimal client-side interactivity it performs well; for sites that are almost entirely static, Astro ships less JavaScript by default.
Can Astro pages use speculation rules?
Yes. Because Astro sites navigate with full page loads by default, speculation rules for prefetching or prerendering likely next pages can make navigations nearly instant.
Do Svelte components hydrate faster than React components?
Generally, because compiled Svelte code and its small runtime do less work per component. The number of components hydrated still matters most.
What are Astro server islands?
Components marked with server:defer that render on the server after the main page, so a cached static page can include personalised or slow content without delaying the rest.
How small can a SvelteKit page's JavaScript be?
Small: the runtime is compact, and with prerendering and few interactive components, routes often ship tens of kilobytes. Check per-route chunks in the build output.
Should I use Astro's view transitions?
They make navigations feel smoother and allow persistent islands, at the cost of a small client router. Measure INP and navigation timing before and after enabling them.
Guides in This Topic
- Choosing Astro client directives — when each island hydrates.
- SvelteKit preloading and load functions — fast data and navigations.
- Astro vs Next.js for content-site performance — choosing a framework for content.
Related
- Framework performance — the section overview.
- Lazy hydration in Nuxt — the Vue take on partial hydration.
- Speculative loading & prefetching — instant navigations for multi-page sites.