Seamless Integration of React and Vue Applications in Astro Using Module Federation
Grace Collins
Solutions Engineer · Leapcell

Introduction
In the rapidly evolving landscape of front-end development, building large-scale applications often involves multiple teams working on different parts of a project, potentially using various frameworks. This scenario frequently leads to the challenge of integrating these disparate parts into a cohesive user experience without sacrificing performance or maintainability. Traditional approaches like IFrames or server-side includes have limitations in terms of communication, styling, and application context sharing. The emergence of meta-frameworks like Astro, designed for optimal performance through island architecture, further amplifies the need for effective integration strategies. This article delves into how Module Federation provides an elegant solution to this very problem, allowing us to seamlessly integrate independent React and Vue applications within an Astro project, thus unlocking greater flexibility and fostering a truly composable front-end architecture.
Core Concepts Explained
Before diving into the implementation details, let's clarify some key terms central to our discussion:
-
Module Federation: A Webpack feature that allows a JavaScript application to dynamically load code from another application and vice-versa. It enables sharing and integrating codebases across different applications, often called "remotes" (the applications providing modules) and "hosts" (the applications consuming them). The key innovation is treating entire applications or components as modules that can be shared and consumed at runtime.
-
Micro-Frontends: An architectural style where a web application is composed of independent front-end applications, each maintained by a separate team. Module Federation is a powerful tool for realizing micro-frontend architectures.
-
Astro: A modern static site builder/meta-framework designed for speed. It uses an "island architecture" where most of the JavaScript is removed from the final build, only hydrating interactive UI components (islands) with JavaScript when needed. This approach typically works with various UI frameworks like React, Vue, Svelte, etc.
-
Host Application: In Module Federation context, this is the application that consumes modules from other applications. In our case, the Astro project will act as the host.
-
Remote Application: An application that exposes modules to be consumed by other applications. Here, our independent React and Vue applications will serve as remotes.
Principle and Implementation
The core principle behind this integration is straightforward: we will configure our independent React and Vue applications to act as Module Federation remotes, exposing their components. Our Astro project, acting as the host, will then consume these exposed components. Astro's inherent ability to support different UI frameworks means it can render these React and Vue components imported via Module Federation just like it would render local components.
Setting up Remote Applications (React and Vue)
First, let's prepare our React and Vue applications to expose components. We'll use Webpack 5's Module Federation Plugin.
React Remote Application Example
Let's assume we have a simple React app that wants to expose a Counter
component.
react-remote/webpack.config.js
:
const HtmlWebPackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const deps = require('./package.json').dependencies; module.exports = { output: { publicPath: 'http://localhost:3001/', // This is crucial for Astro to find the remote }, resolve: { extensions: ['.jsx', '.js', '.json'], }, devServer: { port: 3001, }, module: { rules: [ { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: ['@babel/preset-react'], }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'react_remote', filename: 'remoteEntry.js', exposes: { './CounterApp': './src/CounterApp', // Expose your main component }, shared: { ...deps, react: { singleton: true, requiredVersion: deps.react, }, 'react-dom': { singleton: true, requiredVersion: deps['react-dom'], }, }, }), new HtmlWebPackPlugin({ template: './public/index.html', }), ], };
react-remote/src/CounterApp.jsx
:
import React, { useState } from 'react'; const CounterApp = () => { const [count, setCount] = useState(0); return ( <div style={{ padding: '10px', border: '1px solid blue' }}> <h2>React Counter Component</h2> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default CounterApp;
Vue Remote Application Example
Similarly, a Vue app can expose a Greeting
component.
vue-remote/webpack.config.js
:
const { VueLoaderPlugin } = require('vue-loader'); const HtmlWebPackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const deps = require('./package.json').dependencies; module.exports = { output: { publicPath: 'http://localhost:3002/', // Crucial for Astro }, resolve: { extensions: ['.vue', '.js', '.json'], }, devServer: { port: 3002, }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, }, { test: /\.css$/, use: ['style-loader', 'css-loader'], }, ], }, plugins: [ new VueLoaderPlugin(), new ModuleFederationPlugin({ name: 'vue_remote', filename: 'remoteEntry.js', exposes: { './GreetingApp': './src/GreetingApp.vue', // Expose your main component }, shared: { ...deps, vue: { singleton: true, requiredVersion: deps.vue, }, }, }), new HtmlWebPackPlugin({ template: './public/index.html', }), ], };
vue-remote/src/GreetingApp.vue
:
<template> <div style="padding: 10px; border: 1px solid green;"> <h2>Vue Greeting Component</h2> <p>Hello from Vue!</p> <button @click="greet">Click Me</button> </div> </template> <script> export default { methods: { greet() { alert('Vue says hi!'); } } } </script> <style scoped> /* Scoped styles specific to this component */ </style>
Ensure both remote applications are running (e.g., npm start
in each directory).
Consuming in Astro (Host Application)
Now, let's configure our Astro project to consume these remotes. Astro's src/pages
or src/components
will be the place where we import and render our remote components. Astro typically uses Vite, so we need a way to tell Vite how to handle Module Federation. While Vite doesn't natively support Module Federation like Webpack, there are established plugins that bridge this gap. We'll use vite-plugin-federation
.
astro-host/vite.config.ts
:
import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; // Example for another framework, not strictly needed for MF directly import react from '@vitejs/plugin-react'; import vue from '@vitejs/plugin-vue'; import federation from '@module-federation/vite'; // The plugin for Module Federation export default defineConfig({ plugins: [ react(), vue(), federation({ name: 'astro_host', remotes: { react_remote: 'http://localhost:3001/remoteEntry.js', vue_remote: 'http://localhost:3002/remoteEntry.js', }, shared: ['react', 'react-dom', 'vue'], // Ensure shared dependencies. Astro might not directly use these, but important for remote apps. }), ], });
astro-host/astro.config.mjs
:
import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; import vue from '@astrojs/vue'; // https://astro.build/config export default defineConfig({ integrations: [react(), vue()], vite: { plugins: [ // Make sure the vite.config.ts federation plugin is properly linked // or duplicate the federation config here if not using vite.config.ts for all vite config // For simplicity, we assume vite.config.ts will be picked up by Astro. // If not, you might need to manually import and configure `federation` here. ], }, });
Now, let's create Astro components that import and render these federated components.
astro-host/src/pages/index.astro
:
--- import Layout from '../layouts/Layout.astro'; import ReactCounter from '../components/ReactCounter.astro'; import VueGreeting from '../components/VueGreeting.astro'; --- <Layout title="Module Federation in Astro"> <main> <h1>Welcome to Astro with Module Federation!</h1> <p>This page integrates components from independent React and Vue applications.</p> <div style="margin-top: 20px;"> <h2>React Component Hosted in Astro</h2> <ReactCounter client:load /> {/* client:load hydrates the React component */} </div> <div style="margin-top: 20px;"> <h2>Vue Component Hosted in Astro</h2> <VueGreeting client:load /> {/* client:load hydrates the Vue component */} </div> </main> </Layout> <style> main { margin: auto; padding: 1.5rem; max-width: 60ch; } h1 { font-size: 3rem; font-weight: 800; margin: 0; } </style>
astro-host/src/components/ReactCounter.astro
:
--- import { createRemoteComponent } from '../lib/module-federation'; const RemoteCounter = await createRemoteComponent('react_remote', './CounterApp'); --- <RemoteCounter client:load />
astro-host/src/components/VueGreeting.astro
:
--- import { createRemoteComponent } from '../lib/module-federation'; const RemoteGreeting = await createRemoteComponent('vue_remote', './GreetingApp'); --- <RemoteGreeting client:load />
astro-host/src/lib/module-federation.ts
: (Utility to dynamically load remote components)
import { createComponent } from '@module-federation/sdk'; import type { AstroComponentFactory } from 'astro/runtime/server/index.js'; export async function createRemoteComponent(remoteName: string, moduleName: string): Promise<AstroComponentFactory> { const Component = await createComponent({ remote: remoteName, module: moduleName, scope: remoteName, // Scope usually matches remote name // Important: For Astro, you need to ensure the remote component is treated as a client-side island // The framework adapter (e.g., @astrojs/react, @astrojs/vue) handles the actual hydration }); // This conversion might vary based on how @module-federation/sdk integrates with Astro. // The goal is to get an Astro-compatible component. // For most frameworks, the imported component can be wrapped directly to be used with client: directive. return Component as AstroComponentFactory; }
Note: The createRemoteComponent
implementation might need adjustments based on the exact version and usage of @module-federation/sdk
and Astro's internal component handling. The fundamental idea is to dynamically import the remote module and then render it within Astro. The client:load
directive is crucial for Astro to hydrate these components on the client-side, making them interactive.
Application Scenarios
- Micro-Frontend Architectures: Each independent application (React, Vue) can be developed and deployed independently, owned by different teams. Astro acts as the orchestration layer, composing them into a single product.
- Gradual Migration: When migrating a large application from one framework to another, you can gradually rewrite parts of the application as new micro-frontends and integrate them into the existing shell (Astro in this case).
- Shared Component Libraries: Teams can expose common UI components or even business logic modules as federated modules, allowing other remote or host applications to consume them, ensuring consistency and reusability.
- Leveraging Best-of-Breed: Allows different teams to choose the best framework for their specific features, without forcing a monolithic framework decision on the entire project.
Conclusion
Integrating multiple independent React and Vue applications within an Astro project using Module Federation offers a powerful and flexible approach to modern front-end development. By enabling dynamic code sharing at runtime, Module Federation empowers micro-frontend architectures, facilitates gradual migrations, and promotes code reusability across diverse framework ecosystems. This strategy not only enhances developer experience and empowers team autonomy but also delivers robust, performant, and maintainable web applications by leveraging Astro's speed benefits alongside the versatility of cross-framework component integration. The future of front-end development is increasingly composable, and Module Federation is a cornerstone of this paradigm.