#client:load inside server:defer

1 messages · Page 1 of 1 (latest)

lapis sundial
#

Hey! I am trying to dynamically switch between 2 components depending on a cookie that is set on client-side so my landingpage also works without javascript. The NoJsSwitch-component renders the correct slot, but the DynamicHero-component is not interactive on the client. It looks like this:

index.astro
---
import Layout from "@layouts/Layout.astro";
import DynamicHero from "@components/DynamicHero.vue";
import StaticHero from "@components/StaticHero.astro";
import NoJsSwitch from "@components/NoJsSwitch.astro";
---
<Layout>
    <NoJsSwitch server:defer>
        <DynamicHero client:load slot="js" />
        <StaticHero slot="no-js" />
    </NoJsSwitch>
</Layout>
NoJsSwitch.astro
---
import { USES_JS_KEY } from "@scripts/client-cookie";

const usesJs = Astro.cookies.get(USES_JS_KEY)?.value ?? false;
---

{usesJs ? <slot name="js" /> : <slot name="no-js" />}
DynamicHero.vue
<script setup lang="ts">
import { onMounted } from "vue";

// some stuff

onMounted(() => {
  // some more stuff...
});
</script>

<template>
    <!-- more stuff -->
</template>
primal cloudBOT
#

Hi @lapis sundial. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

The issue you're running into is a known limitation with how client:* directives work in Astro. According to the docs:

A client directive can only be used on a UI framework component that is directly imported into a .astro component. Hydration directives are not supported when using custom components passed via the components prop [or, by extension, slots in this context].

[Client Directives]

When you pass <DynamicHero client:load slot="js" /> as a slot child to NoJsSwitch, the client:load directive is not being processed correctly because the hydration happens at the level of the parent .astro component — but the component is being passed through a slot into a server island (server:defer), which breaks the direct-import requirement for hydration directives.

Additionally, the server:defer directive causes NoJsSwitch to be rendered in a separate deferred request. Props passed to server islands must be serializable, and slots/components passed through them have their own constraints. [Passing Props to Server Islands]


What you can do instead

Rather than passing <DynamicHero client:load /> as a slot into a server island, move the conditional logic directly into your server island component, and render the Vue component from within it:

NoJsSwitch.astro

---
import { USES_JS_KEY } from "@scripts/client-cookie";
import DynamicHero from "@components/DynamicHero.vue";
import StaticHero from "@components/StaticHero.astro";

const usesJs = Astro.cookies.get(USES_JS_KEY)?.value ?? false;
---

{usesJs ? <DynamicHero client:load /> : <StaticHero />}
#

index.astro

---
import Layout from "@layouts/Layout.astro";
import NoJsSwitch from "@components/NoJsSwitch.astro";
---
<Layout>
    <NoJsSwitch server:defer />
</Layout>

This way:

  • NoJsSwitch is a server island that reads the cookie on the server (via Astro.cookies). [Server Islands]
  • DynamicHero is directly used inside the .astro component with client:load, satisfying the hydration directive requirement.
  • StaticHero renders as plain HTML when JS is not detected.

You can also add fallback content to NoJsSwitch for the brief moment before the island loads:

<NoJsSwitch server:defer>
    <StaticHero slot="fallback" />
</NoJsSwitch>

[Server Island Fallback]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: