npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Run React, Vue, Svelte, Solid, and Preact side-by-side in one Astro project with full framework isolation and shared reactive state via nanostores.
Provides expertise in Astro for islands architecture, selective hydration (client:load/idle/visible/media), content layer API, server islands, SSR adapters, view transitions, i18n, actions, and React/Vue/Svelte/Solid integrations.
Implements Astro Islands Architecture: ship zero JS by default, hydrate interactive components (React, Vue, Svelte) with client:* directives (load, idle, visible, media, only). Audits over-hydration.
Provides Astro framework patterns for islands architecture, content collections, rendering strategies (SSG, SSR, hybrid), view transitions, partial hydration, and Cloudflare deployment.
Share bugs, ideas, or general feedback.
Run React, Vue, Svelte, Solid, and Preact side-by-side in one Astro project with full framework isolation and shared reactive state via nanostores.
npx astro add react vue svelte solid
This updates astro.config.mjs automatically:
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import vue from '@astrojs/vue';
import svelte from '@astrojs/svelte';
import solid from '@astrojs/solid-js';
export default defineConfig({
integrations: [react(), vue(), svelte(), solid()],
});
.astro file. Apply client:* directives as normal:---
import ReactCounter from './ReactCounter.jsx';
import VueDatePicker from './VueDatePicker.vue';
import SvelteModal from './SvelteModal.svelte';
---
<!-- Each island hydrates independently -->
<ReactCounter client:load initialCount={0} />
<VueDatePicker client:idle />
<SvelteModal client:visible triggerText="Open" />
Framework components are fully isolated. A React component cannot import a Vue component directly. Cross-framework composition must happen at the .astro level — use .astro as the orchestration layer.
Share reactive state between framework components using nanostores. Install framework-specific adapters:
npm install nanostores @nanostores/react @nanostores/vue
// src/stores/cart.ts
import { atom, computed } from 'nanostores';
export const cartItems = atom<CartItem[]>([]);
export const cartCount = computed(cartItems, (items) => items.length);
export function addToCart(item: CartItem) {
cartItems.set([...cartItems.get(), item]);
}
// React: src/components/CartBadge.jsx
import { useStore } from '@nanostores/react';
import { cartCount } from '../stores/cart';
export function CartBadge() {
const count = useStore(cartCount);
return <span className="badge">{count}</span>;
}
<!-- Vue: src/components/CartBadge.vue -->
<script setup>
import { useStore } from '@nanostores/vue';
import { cartCount } from '../stores/cart';
const count = useStore(cartCount);
</script>
<template>
<span class="badge">{{ count }}</span>
</template>
<!-- Svelte: src/components/CartBadge.svelte -->
<script>
import { cartItems } from '../stores/cart';
</script>
<span class="badge">{$cartItems.length}</span>
For state that does not need reactivity, use localStorage or URL params as a simple shared medium between islands. For complex cross-island workflows, prefer nanostores over custom events.
When mixing React and Solid, disambiguate JSX transforms in tsconfig.json using include/exclude or separate tsconfig files per framework directory. Both use JSX but with different transforms.
Use client:only="react" (or the relevant framework) for components that rely heavily on browser APIs and should not attempt SSR from the wrong framework context.
Astro's multi-framework support is powered by its renderer abstraction. Each framework integration (@astrojs/react, etc.) registers a renderer that tells Astro's build system how to SSR and hydrate components of that type. The renderers are completely independent — React's useState and Vue's ref never conflict because they hydrate in separate DOM trees.
Why nanostores?
Nanostores is framework-agnostic by design. Each atom is a tiny observable that any framework adapter can subscribe to. When a nanostore value changes, only the components subscribed to that specific atom re-render — regardless of which framework they use. This is exactly the right primitive for cross-island communication.
Alternatives and their trade-offs:
window.dispatchEvent) — works everywhere but requires manual subscription management and is not reactive in the framework sensehistory.pushState to trigger re-rendersJSX disambiguation:
When React and Solid coexist, both use JSX syntax but with different runtime imports. Astro uses the file extension to determine which renderer to use (.jsx/.tsx for React, .jsx/.tsx in a /solid/ directory for Solid). Configure jsxImportSource per directory in tsconfig.json if you have both React and Solid .tsx files:
// tsconfig.json (for React default)
{ "compilerOptions": { "jsxImportSource": "react" } }
Use /** @jsxImportSource solid-js */ pragma at the top of Solid files when in a mixed project.
Bundle implications:
Each framework ships its own runtime bundle. Having React + Vue + Svelte + Solid on the same page means shipping all four runtimes to the client (when using client:* directives from all four). Be strategic: prefer one primary framework for most interactive islands, and use secondary frameworks only for specific components that justify the added bytes.
Migration pattern:
Multi-framework support is ideal for incremental migrations. Start with your legacy framework (Vue 2, say), add the new framework (@astrojs/react), and rewrite components one at a time. Both frameworks work in production throughout the migration.
https://docs.astro.build/en/guides/framework-components