From harness-claude
Runs React, Vue, Svelte, Solid, and Preact components side-by-side in one Astro project with full framework isolation and shared state via nanostores. Useful for migrations, combining framework-specific libraries, or evaluating frameworks on the same site.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:astro-multi-frameworkThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Run React, Vue, Svelte, Solid, and Preact side-by-side in one Astro project with full framework isolation and shared reactive state via nanostores.
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
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeExpert Astro UI framework integrations — @astrojs/react, @astrojs/vue, @astrojs/svelte, @astrojs/solid-js, @astrojs/preact, @astrojs/alpinejs, @astrojs/lit, @qwikdev/astro, @analogjs/astro-angular. Setup, multi-framework config, usage patterns.
Guides use of Astro's islands architecture: choosing client:* directives, minimizing JS, and auditing hydration for mixed static/interactive pages.
Guides Astro rendering strategy decisions (SSG, SSR, hybrid), islands architecture with hydration directives, and content collections. Includes adapter configuration for Cloudflare and other platforms.