There's a race condition in Astro's hydration process when using Svelte 5 components with the context API. Child components calling getContext()
are hydrated before parent components calling setContext()
, causing context to be undefined and breaking component communication.
- Astro Version: ^5.11.2
- Svelte Version: ^5.36.5 with Runes
- Astro Svelte Integration: @astrojs/svelte ^7.1.0
- TypeScript: ^5.8.3
- Hydration Strategy:
client:load
- Architecture: Static-first with Astro islands
When using Svelte 5's context API within Astro islands, child components consistently hydrate before their parent components, causing getContext()
to return undefined
because setContext()
hasn't been called yet.
Parent components should hydrate first and call setContext()
before child components hydrate and call getContext()
.
Child components hydrate first, call getContext()
, receive undefined
, and fail to establish proper parent-child communication.
<script lang="ts">
import type { FormContext } from "@/types"
import { setContext } from "svelte"
interface Props {
children?: any
}
let { children }: Props = $props()
console.log("FormWrapper: Starting initialization")
const formContext: FormContext = $state({
submitMessage: "",
setSubmitMessage: (message: string) => {
formContext.submitMessage = message
},
})
console.log("FormWrapper: Setting context")
setContext("form", formContext)
console.log("FormWrapper: Context set complete")
</script>
<form>
{@render children?.()}
</form>
<script lang="ts">
import type { FormContext } from "@/types"
import { getContext } from "svelte"
import type { HTMLInputAttributes } from "svelte/elements"
interface Props extends HTMLInputAttributes {}
let { name, type = "text", placeholder }: Props = $props()
console.log("FormInput: Starting initialization")
const formContext = getContext<FormContext>("form")
console.log("FormInput: Context received:", $inspect(formContext))
if (!formContext) {
console.error("FormInput: No form context found!")
}
</script>
<input
{name}
{type}
{placeholder}
/>
---
// src/pages/index.astro
import FormInput from "@components/FormInput.svelte"
import FormWrapper from "@components/FormWrapper.svelte"
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<title>GitHub Issue Test - Astro Svelte Context Race Condition</title>
</head>
<body>
<FormWrapper client:load>
<FormInput
name="email"
type="email"
placeholder="Enter email"
/>
</FormWrapper>
</body>
</html>
FormInput: Starting initialization
FormInput: Context received: undefined
FormInput: No form context found!
FormWrapper: Starting initialization
FormWrapper: Setting context
FormWrapper: Context set complete
The console logs clearly show the timing issue:
FormInput
initializes first and callsgetContext()
→ receivesundefined
FormWrapper
initializes second and callssetContext()
→ too late
Creating an intermediary Svelte component that wraps both parent and child resolves the issue:
<!-- FormNewsletter.svelte -->
<script lang="ts">
import FormWrapper from "./FormWrapper.svelte"
import FormInput from "./FormInput.svelte"
</script>
<FormWrapper>
<FormInput
name="email"
type="email"
placeholder="Enter email"
/>
</FormWrapper>
<!-- In Astro page -->
<FormNewsletter client:load />
This works because both components are now in the same Svelte compilation context and hydrate together.
The issue appears to be in Astro's hydration process where:
- Individual Components as Islands: When
FormWrapper
is used directly as an Astro island, each Svelte component becomes a separate hydration unit - Hydration Order: Astro hydrates components in DOM order (children before parents) rather than logical dependency order
- Context Isolation: Each component hydrates independently without awareness of context dependencies
Astro could analyze Svelte components for context dependencies and adjust hydration order accordingly.
Implement a bridge that allows context to be established across separate hydration boundaries.
Allow getContext()
to return a promise or reactive value that resolves when context becomes available.
Provide a way to group related components so they hydrate together as a unit.
This appears to be a broader issue affecting multiple frameworks:
- SvelteKit: Similar context timing issues reported in issue #1171
- Flowbite-Svelte: Context hydration problems documented in issue #1200
- Pattern: Common across SSR/hydration scenarios with context APIs
- Severity: High - Breaks fundamental parent-child communication patterns
- Frequency: Consistent - Occurs every time context API is used across Astro islands
- Workaround: Available but requires architectural changes
bug
svelte
hydration
context
islands
priority:high
- Astro Integration:
@astrojs/svelte
- Hydration Strategy:
client:load
(affects all strategies) - Component Architecture: Parent/child with context communication
- Reproduction Rate: 100% consistent
This issue specifically affects Astro's islands architecture where components are hydrated independently. The problem doesn't occur in traditional SPA scenarios where all components hydrate together in a single context.
The workaround of using intermediary components is functional but forces architectural compromises and reduces the flexibility of Astro's component model.