Type something to search...
How to mix multiple UI frameworks in one Astro project?

How to mix multiple UI frameworks in one Astro project?

To mix multiple UI frameworks in one Astro project, install an integration for each one, for example npx astro add react vue svelte, then import components from any of them into the same .astro page. Astro picks the right renderer from each file's extension, so .vue, .svelte and .jsx/.tsx files can sit side by side with no extra configuration. The one exception is when you use two JSX frameworks together, such as React and Preact or React and Solid: they share file extensions, so you must tell each integration which files are its own with the include and exclude options. Each hydrated component becomes its own island, and each framework's runtime is only downloaded on pages that hydrate one of its components.

Mixing frameworks is unusual in most tools but routine in Astro, because every island is rendered and hydrated independently. It's useful for gradual migrations, for reusing a component that only exists in one ecosystem, or for teams with different preferences. In this article you'll learn how to install several frameworks, how to configure JSX frameworks so they don't clash, how TypeScript fits in, how to nest components from different frameworks, how to share state between them, and what it costs in JavaScript.

Installing Several Framework Integrations

astro add accepts several integrations at once:

npx astro add react vue svelte

It installs the packages and adds each integration to your config:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import svelte from '@astrojs/svelte';
import vue from '@astrojs/vue';

export default defineConfig({
  integrations: [react(), vue(), svelte()],
});

When I ran this command, it also printed a note about TypeScript:

Caution: Selected UI frameworks require conflicting tsconfig.json settings, as such only settings for react were used.

That refers to the jsx and jsxImportSource settings, which only one JSX framework can own. It doesn't matter for React plus Vue plus Svelte, because Vue and Svelte single-file components don't use those settings. It does matter once a second JSX framework is involved, as covered below.

Using Components from Different Frameworks Together

With the integrations installed, import components from each framework into one page:

---
// src/pages/index.astro
import Counter from '../components/react/Counter';
import VueCounter from '../components/VueCounter.vue';
import SvelteCounter from '../components/SvelteCounter.svelte';
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Mixed frameworks</title>
  </head>
  <body>
    <Counter label="React" client:load />
    <VueCounter label="Vue" client:idle />
    <SvelteCounter label="Svelte" client:visible />
  </body>
</html>

Each component is rendered to HTML on the server by its own framework, then hydrated in the browser according to its directive. Writing components for each framework is covered in the dedicated posts on React, Vue and Svelte.

Only .astro files can combine frameworks. A .tsx file can't import a .vue component and a .svelte file can't import a React one, because each is compiled by its own framework.

Combining Two JSX Frameworks

React, Preact and Solid all use .jsx and .tsx files, so Astro can't tell from the extension which one should compile a file. When I added Preact to a project that already had React and left the defaults in place, the build warned:

[WARN] [@astrojs/react] More than one JSX renderer is enabled. This will lead to unexpected behavior unless you set the `include` or `exclude` option.
[WARN] [@astrojs/preact] More than one JSX renderer is enabled. This will lead to unexpected behavior unless you set the `include` or `exclude` option.

It then failed to prerender a page with a React counter:

[ERROR] [build] Caught error rendering /below: Error: Unable to render Counter!
...
3. If using multiple JSX frameworks at the same time (e.g. React + Preact), pass the correct `include`/`exclude` options to integrations.

The fix is to put each framework's components in their own folder and point each integration at it:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import react from '@astrojs/react';
import svelte from '@astrojs/svelte';
import vue from '@astrojs/vue';

export default defineConfig({
  integrations: [
    react({ include: ['**/react/*'] }),
    preact({ include: ['**/preact/*'] }),
    vue(),
    svelte(),
  ],
});

With this layout:

src/components/
├── react/
│   └── Counter.tsx
├── preact/
│   └── Rating.tsx
├── VueCounter.vue
└── SvelteCounter.svelte

include is required for each JSX framework once there's more than one; exclude is optional and accepts the same kind of glob patterns. Vue and Svelte don't need either, because their extensions are unique.

Vue JSX Counts as a JSX Framework

If you enable vue({ jsx: true }) so Vue components can be written in JSX, Vue joins the JSX group. In my test, turning that on alongside React and Preact produced the same "More than one JSX renderer is enabled" warning from @astrojs/vue. Give it an include pattern too, or leave Vue JSX off if you only write .vue files.

TypeScript with Multiple JSX Frameworks

The include option controls how files are compiled, but there's a second, separate setting: jsxImportSource in tsconfig.json. TypeScript can only have one value there, and it affected my build too.

When I ran npx astro add preact in a project that already had React, it changed the setting to Preact:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

With that value, React components still failed to render even after adding the include options. Switching it back to react fixed the build. The approach recommended in the Astro docs is:

  1. Set jsxImportSource to your most-used JSX framework.
  2. Add a pragma comment at the top of each file that belongs to a different JSX framework.
// src/components/preact/Rating.tsx
/** @jsxImportSource preact */
import { useState } from 'preact/hooks';

export default function Rating({ max = 5 }: { max?: number }) {
  const [value, setValue] = useState(0);

  return (
    <div>
      {Array.from({ length: max }, (_, i) => (
        <button type="button" aria-pressed={i < value} onClick={() => setValue(i + 1)}>
          {i < value ? '★' : '☆'}
        </button>
      ))}
    </div>
  );
}

For Solid files the pragma is /** @jsxImportSource solid-js */. With the include options, jsxImportSource: "react" and the pragma in Preact files, my test project built cleanly and astro check reported no errors.

After running astro add for a new JSX framework, always check what it did to tsconfig.json.

Nesting Components from Different Frameworks

Inside an .astro file, the children you pass to a framework component can include hydrated components from other frameworks. This puts a React island inside a Svelte card:

---
// src/pages/index.astro
import Counter from '../components/react/Counter';
import SvelteCard from '../components/SvelteCard.svelte';
---
<SvelteCard>
  <h2 slot="title">Nested</h2>
  <Counter label="React inside Svelte" client:load />
</SvelteCard>

Here the Svelte card has no directive, so it's static HTML, and the React counter inside it is a hydrated island. In Chrome, clicking it updated the count as usual. You can also hydrate the outer component; Astro waits for a parent island to hydrate before hydrating the islands inside it.

Two rules apply to nesting:

  • Pass children through slots, not imports: the Svelte file doesn't know about React. Astro renders the React component and hands its HTML to the Svelte component as slot content.
  • No render props: you can't pass functions from Astro to framework components, so patterns like React render props don't work across frameworks. Use named slots instead.

Slot handling also differs per framework: React, Preact and Solid receive named slots as top-level props with kebab-case names converted to camelCase, while Vue and Svelte keep slot names exactly as written.

Sharing State Between Frameworks

Each island runs in isolation, and framework-specific tools like React Context or Svelte's setContext() don't reach islands from another framework. A framework-agnostic store fixes this. Nano Stores has helpers for React, Preact, Vue and Solid, and works with Svelte's $ syntax directly, so one store can connect all of them. A full example with React, Vue and Svelte islands sharing a cart is in how to share state between islands.

What It Costs in JavaScript

Every framework you hydrate adds its runtime to the page. Astro only sends a runtime once per page, no matter how many islands use it, and only on pages that hydrate at least one component from that framework. Static framework components cost nothing.

I built a test page with React, Preact, Vue and Svelte islands and measured the main chunks, gzipped:

FrameworkApproximate runtime cost (gzipped)
Reactabout 66 KB for the renderer chunk, plus about 3 KB
Vueabout 29 KB (runtime plus Astro's Vue client)
Svelteabout 12 to 14 KB
Preactabout 10 KB including hooks and signals

These numbers come from one Astro 7.4.2 test build and will vary with versions and features used, but the proportions are a useful guide. In Chrome, the page requested each framework's runtime chunks once, and each component's own chunk was only a few hundred bytes.

The practical advice:

  • Hydrate one framework per page where you can: three frameworks on one page means three runtimes.
  • Keep secondary frameworks to the pages that need them: a Vue chart on one report page doesn't affect the rest of the site.
  • Leave static components static: a React layout component without a directive is free.

When Mixing Frameworks Makes Sense

  • Incremental migration: move a site from one framework to another page by page or component by component, without a rewrite.
  • Reusing an ecosystem component: a calendar, editor or chart that only exists for one framework can be dropped into a site built mostly with another.
  • Different teams: teams that own separate sections can use what they know, as long as pages stay reasonably light.
  • Learning: try a new framework in an existing project without starting from scratch.

If none of these apply, sticking to one framework is simpler: one set of conventions, one runtime, and no JSX configuration to maintain. Astro's official list of framework integrations and how to add others is covered in Astro integrations.


FAQ: Multiple UI Frameworks in Astro

Yes. Install both integrations and import components from each into an .astro file. No extra configuration is needed because .vue files and .jsx or .tsx files are never ambiguous.

Both use .jsx and .tsx files, so Astro can't tell which framework should compile each file. Set include (and optionally exclude) on each integration, check that jsxImportSource in tsconfig.json matches your main framework, and add a jsxImportSource pragma to files from the other one.

No. Framework component files can only use their own framework. Combine them in an .astro file, passing one framework's components into another's slots.

Only on pages that hydrate components from several frameworks, because each one adds its runtime. Pages that only render static framework components ship no framework JavaScript at all.

No. Each framework's runtime is downloaded once per page and shared by every island from that framework on the page.

React, Preact, Svelte, Vue, SolidJS and AlpineJS have official integrations. Community integrations exist for others such as Angular and Qwik.


Conclusion

Astro makes mixing frameworks almost effortless. Install an integration for each, import their components into .astro files, and each one renders on the server and hydrates as its own island. Vue and Svelte need no extra setup alongside React; two JSX frameworks need include patterns, a matching jsxImportSource in tsconfig.json, and a pragma comment in files from the secondary framework.

The flexibility is real, but so is the cost: every hydrated framework adds its runtime to the page. Use multiple frameworks where they earn their place, such as migrations or a component only one ecosystem provides, keep each page to as few hydrated frameworks as practical, and use a shared store like Nano Stores when islands from different frameworks need to work together.

Tags :
Share :

Related Posts

What are Astro Actions and how do they work?

What are Astro Actions and how do they work?

Astro Actions are type-safe backend functions that you define once on the server and then call from the browser as if they were ordinary async fu

Dive Deeper
What are Astro adapters and which one should you choose?

What are Astro adapters and which one should you choose?

An Astro adapter is an integration that builds the server part of your project for a specific runtime, such as Node.js, Vercel Functions, Netlify

Dive Deeper
How to build API endpoints in Astro?

How to build API endpoints in Astro?

To build an API endpoint in Astro, create a .js or .ts file in src/pages/ and export a function named after an HTTP method, such as GET or

Dive Deeper