Performance
Tips and patterns for keeping your Avalon site fast.
Zero JS by default
The biggest performance win is already built in: pages ship no JavaScript unless you add an island. A page with no islands is pure HTML and CSS — no hydration, no runtime overhead.
Choose the right hydration condition
Each hydration condition has a different impact on page load:
| Condition | JS on initial load | Best for |
|---|---|---|
on:client | Yes — blocks critical path | Hero animations, auth-gated UI |
on:visible | Deferred until scroll | Below-the-fold content |
on:interaction | Deferred until click/hover | Forms, menus, dropdowns |
on:idle | Background, lowest priority | Analytics, chat widgets |
media: | Only if query matches | Responsive-only components |
ssr: false | Same as its condition (default on:client) | Components that cannot run on the server (window, maps, embeds) |
ssr: false is not a condition — it skips SSR. The page ships an empty placeholder and no no-JS fallback for that island. Prefer a normal (SSR + hydrate) island when the component can render without browser APIs.
Prefer on:interaction or on:idle for anything that doesn't need to be interactive immediately.
Client navigation
With clientRouter: true, internal links fetch the next SSR HTML document and swap it in instead of reloading. That avoids re-downloading the shared runtime and reused stylesheets. Layout islands that should not reset use island={{ persist: 'key' }}. Routes that must not swap export clientNavigation = false. See Client Navigation.
Lazy integrations
Enable lazyIntegrations to avoid loading unused framework adapters:
const plugins = await avalon({
integrations: ['react', 'vue', 'svelte'],
lazyIntegrations: true,
});
With lazy integrations, the adapter for each framework is only loaded when a page actually uses an island from that framework.
Image optimization
Avalon includes a built-in <Image> component that handles format conversion, responsive srcset generation, and lazy loading automatically:
import { Image } from '@useavalon/avalon/client';
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} />
The component converts images to modern formats (WebP, AVIF), generates multiple sizes for responsive layouts, and adds width/height attributes to prevent layout shift. See the full Image Optimization guide for configuration options.
Streaming SSR
Avalon streams the layout shell to the browser before page data resolves. This dramatically improves FCP on pages with async data fetching — the user sees the navigation and layout instantly while the server waits on API calls. Streaming is automatic for all pages using the modular layout system. See Streaming SSR for details.
Caching
Avalon merges default cache headers for hashed assets (/assets/**, /islands/**, /chunks/**). For SSR pages and APIs, use Nitro’s server cache via routeRules, defineCachedHandler, and defineCachedFunction. See the Caching guide.
Bundle analysis
Inspect your island bundles with Vite's built-in rollup visualizer:
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [visualizer({ open: true })],
});
Preloading critical islands
on:client islands automatically emit <link rel="modulepreload"> during SSR so the browser can fetch island chunks in parallel with HTML parse. For large below-the-fold islands, opt out so JS does not compete with first paint:
<HeavyDashboard island={{ condition: 'on:client', preload: false }} />
Tune priority on the preload link when the island is not critical for LCP:
<Sidebar island={{ condition: 'on:client', fetchPriority: 'low' }} />
Deferred conditions (on:visible, on:idle, on:interaction, media:*) never get modulepreload — preloading would defeat lazy hydration.
Measuring
Use PageSpeed Insights or Lighthouse to measure real-world performance. The key metrics to watch are LCP (Largest Contentful Paint) and TBT (Total Blocking Time) — both improve significantly when you defer or eliminate unnecessary JavaScript.