-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Description
Astro Info
Astro v6.0.0-beta.14
Node v25.6.1
System Windows (x64)
Package Manager pnpm
Output static
Adapter none
Integrations @astrojs/svelte
@astrojs/mdx
@vite-pwa/astro-integration
@astrojs/sitemap
If this issue only occurs in one browser, which browser is a problem?
No response
Describe the Bug
Astro Bug Report: Client Directives Still Include JS in Critical Rendering Path
Description
All Astro client directives (client:load, client:idle, client:only) bundle JavaScript in the initial page load, causing non-critical components to block LCP (Largest Contentful Paint) measurement. There appears to be no way to truly defer a component's JavaScript until after the window load event completes.
Issue
We have a decorative FloatingImages Svelte component that should load after LCP to avoid impacting performance metrics. Despite trying all available client directives, the component's JavaScript always appears in the critical rendering path and delays LCP measurement.
Steps to Reproduce
- Create a Svelte component wrapped in an Astro component
- Use any client directive:
client:load,client:idle,client:only="svelte", orclient:media - Build the site and deploy
- Run Lighthouse performance test
- Observe Network Dependency Tree in Lighthouse report
Expected Behavior
With client:load, the component's JavaScript should:
- Not be included in the initial HTML response
- Start downloading after the window
loadevent fires - Not appear in Lighthouse's critical rendering path
- Not impact LCP measurement
With client:only, the component should:
- Skip server-side rendering (this works)
- Have its JavaScript completely deferred from initial bundle
- Load asynchronously after page is interactive
Actual Behavior
All client directives include the component's JavaScript in the initial page bundle:
Network Dependency Tree (from Lighthouse):
Initial Navigation
https://vashonmesh.org - 76 ms, 28.31 KiB
├── /_astro/FloatingImages.DIgMZlMV.js - 247 ms, 2.57 KiB
├── /_astro/client.svelte.CX8gQjlJ.js - 247 ms, 0.67 KiB
├── /_astro/template.Cwu_fZMk.js - 504 ms, 8.62 KiB
├── /_astro/render.BvwD2nt8.js - 503 ms, 2.96 KiB
└── /_astro/lifecycle.DoIy0IOf.js - 503 ms, 2.22 KiB
Result:
- LCP increased from 1.2s → 8.3s
- Component JavaScript in critical path despite
client:only="svelte" - No apparent way to defer loading until after LCP measurement
Tested Workarounds
Attempt 1: client:idle
<FloatingImagesClient client:idle />- Result: Scripts load before window load event
- LCP Impact: 8.9s
Attempt 2: client:load
<FloatingImagesClient client:load />- Result: Scripts still bundled in initial HTML, just delayed execution
- LCP Impact: 3.0s
Attempt 3: client:only="svelte"
<FloatingImagesClient client:only="svelte" />- Result: Skips SSR but JavaScript still in critical path
- LCP Impact: 8.3s
Attempt 4: Manual Chunk Splitting
// astro.config.mjs
build: {
rollupOptions: {
output: {
manualChunks: (id) => {
if (id.includes("FloatingImages")) return "floating-images";
};
}
}
}- Result: Build errors (missing Zod dependencies from Astro internals)
Attempt 5: requestIdleCallback in Svelte
onMount(() => {
if ('requestIdleCallback' in window) {
requestIdleCallback(startAnimation, { timeout: 2000 });
}
});- Result: Only delays animation start, not JavaScript loading
- LCP Impact: Still 8.3s (scripts loaded early)
Code Example
FloatingImages.astro:
---
import FloatingImagesClient from "./FloatingImages.svelte";
const { imageCount = 12 } = Astro.props;
// ... image optimization code ...
---
<FloatingImagesClient
client:only="svelte"
floatingImageItems={floatingImageItems}
keyframesCss={keyframesCss}
/>Layout.astro:
{floatingImages && <FloatingImages imageCount={imageCount} />}Environment
- Astro: 6.0.0-beta.14
- Svelte Integration: @astrojs/svelte
- Build Tool: Vite
- Node: 22.12.0
- Package Manager: pnpm
Desired Solution
One of:
-
New directive:
client:deferthat:- Completely excludes component from initial bundle
- Loads JavaScript only after window
loadevent + requestIdleCallback - Does not appear in Lighthouse critical rendering path
-
Enhanced
client:load:- Should truly defer loading until after window load
- Should not bundle scripts in initial HTML response
- Should use dynamic import at runtime
-
Configuration option:
<Component client:load={{ defer: true, priority: "low" }} />
-
Documentation: If this is by design, document how to achieve true deferred loading for non-critical components
Impact
This limitation makes it impossible to have decorative/non-essential components that don't impact Core Web Vitals (LCP, TBT). For sites focused on performance scores (Google PageSpeed, Lighthouse), this forces removal of visual enhancements.
Temporary Solution
Currently, we've completely disabled the component:
<!-- FloatingImages temporarily disabled to fix LCP --><!-- TODO: Need Astro support for truly deferred component loading -->This is not ideal as the feature adds visual interest to the site.
References
- Astro Islands Documentation
- Client Directives Reference
- Web.dev: Optimize LCP
- Chrome Lighthouse Network Dependency Analysis
Additional Context
Our memory notes from multiple debugging sessions:
- ANY hydration directive except `client:only` adds JS to initial bundle (proven false - client:only also bundles)
- `client:idle` fires too early, before window load
- `client:load` waits for window load BUT still bundles scripts in initial HTML
- Manual chunk splitting causes build errors (missing Zod dependencies)
- Dynamic imports of Astro components not supported at runtime
We've spent considerable time trying every available approach and conclude this is a framework limitation rather than implementation error.
What's the expected result?
See above
Link to Minimal Reproducible Example
See main bug report
Participation
- I am willing to submit a pull request for this issue.