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.
client:load
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 calls getContext()
→ receives undefined
FormWrapper
initializes second and calls setContext()
→ too lateCreating 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:
FormWrapper
is used directly as an Astro island, each Svelte component becomes a separate hydration unitAstro 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:
bug
svelte
hydration
context
islands
priority:high
@astrojs/svelte
client:load
(affects all strategies)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.