Introduction
Ever felt like your frontend monolith is becoming an unmanageable beast? You’re not alone. That’s where microfrontends come in. They let you split your frontend into bite-sized apps that are easier to build, scale, and deploy—without stepping on each other’s toes.
In this tutorial, we’ll walk you through how to deploy a microfrontend architecture where Angular acts as the host app and React serves as the remote app. By the end, you’ll know how to set up, integrate, and deploy these apps seamlessly using Module Federation.
Understanding Microfrontends
In simple terms, microfrontends are an architectural style where a web application is broken down into smaller, independent parts. Each part can be developed, deployed, and maintained autonomously by different teams.
This approach stands in stark contrast to monolithic architectures, where the entire frontend codebase exists as a single, tightly coupled unit. While monoliths can be simpler to start with, they often lead to scalability challenges, slower development cycles, and increased risks during deployment as the application grows.
Benefits
- Scalability: Teams can work on different parts of the application simultaneously without stepping on each other’s toes.
- Independent Deployment: Each microfrontend can be deployed independently, reducing the risk of downtime for the entire application and enabling faster release cycles.
- Tech Stack Flexibility: Teams can choose the best technology for their specific microfrontend, allowing for the use of multiple frameworks (like Angular and React in our case) within the same large application.
- Improved Team Autonomy: Development teams have full ownership of their microfrontends, fostering greater accountability and faster decision-making.
- Esier Maintenance: Smaller, focused codebase are easier to understand, debug, and maintain.
Tech Stack & Tools Needed
To successfully deploy a microfrontend application combining Angular and React, you’ll need the following tools and technologies:
- Node.js and npm/yarn: Essential for running JavaScript environments, managing packages, and executing build scripts.
- Angular CLI: The command-line interface for Angular, used to initialize, develop, and maintain Angular applications.
- React Development Environment: You can choose between:
- Create React App: A popular toll for setting up new React projects with no build configuration. (Soon to be deprecated)
- Vite: A newer, faster build tool that provides a lightning-fast development experience for React and other frameworks. (Recommended)
- Module Federation (Webpack 5): A powerful feature of Webpack 5 that enables multiple separate builds to form a single application. It allows applications to dynamically load code from other applications at runtime, making it ideal for microfrontend architectures.
- Hosting Providers: A platform to host your microfrontend applications. Popular choices include:
- Netlify: Offers continuous deployment, serverless functions, and a global CDN.
- Vercel: Provides a platform for frontend frameworks and static sites, with built-in CI/CD.
- AWS S3 (with CloudFront): A highly scalable and durable object storage service that can be used to host static websites, often combined wit CloudFont for content delivery.
Setting Up the Microfrontend Architecture
Module Federation, a feature introduced in Webpack 5, is a cornerstone for building robust microfrontend architectures. It allows different applications (or parts of applications) to expose and consume modules from each other at runtime, effectively creating a single, unified application from multiple independent builds.
Overview of Module Federation
Module federation operates on the concept of hosts and remotes:
- Host (Shell) Application: This is the main application that consumes modules exposed by other applications. It acts as the entry point for the user and orchestrates the loading of various microfrontends.
- Remote Application: These are the independent microfrontends that expose their components, services, or other modules to be consumed by host applications.
Our architecture design will involve an Angular application acting as the host (shell) and a React application serving as a remote. This means the Angular shell will be responsible for bootstrapping the overall application and dynamically loading React components as needed.
Step 1: Set up the Angular Shell (Host)
First, let’s establish our Angular shell application, which will serve as the host for our React remote.2.
- Initialize Angular Project:
ng new angular-shell --routing --strict cd angular-shell - Add Webpack module federation plugin:
Module Federation isn’t native to Angular CLI out-of-the-box, so we’ll use a helper library like@angular-architects/module-federationng add @angular-architects/module-federation --project angular-shell --port 4200
This command will set up the necessarywebpack.config.jsand other - Define remote entry points:
In yourwebpack.config.js(ormodule-federation.config.js), you’ll define the remotes that your Angular shell will consume. Initially, this might be empty, but we’ll populate it once our React app is ready.
You’ll also configure the host to expose any modules if it needs to be consumed by other potential remotes (though for this setup, Angular is primarily the host).
Step 2: Create React Remote Application
Next, we’ll set up our React application, which will expose components to be consumed by the Angular shell.
- Set up a new React app:
We will use Vite for a faster development experience.npm create vite@latest react-remote -- --template react-ts
cd react-remote
npm install
- Expose components via Module Federation:
You’ll need to configure Webpack (or a Vite plugin for Module Federation) in your React project to expose the components you want to share.
If using Vite, you’d typically use a plugin likevite-plugin-module-federation. Install it:npm install vite-plugin-module-federation -D
Then, configure yourvite.config.ts:
import {defineConfig} from 'vite';
import react from '@vitejs/plugin-react';
import federation from '@module-federation/vite';
export default defineConfig({
plugins: [
react(),
federation({
name: 'react_remote',
filename: 'remoteEntry.js',
exposes: {
'./Counter': './src/components/Counter.tsx',
},
shared: ['react', 'react-dom'],
}),
],
build: {
target: 'esnext',
minify: false,
cssCodeSplit: false,
},
}) - Share dependencies:
Crucially, in yourwebpack.config.js(or Vite federation config) for both Angular and React, you should specify shared dependencies. This helps prevent issues like multiple instances of React or Angular being loaded, leading to larger bundle sizes and potential conflicts. Common shared dependencies includereact,react-dom,@angular/core,rxjs, etc., configured to besingleton: trueandstrictVersion: truewhere appropriate.
Integrating Angular and React
Now that both our Angular host and React remote are set up, let’s connect them.
The primary method for loading React components inside Angular (and vice versa) in a Module Federation context involves dynamic loading.
- Loading React Components inside Angular:
In your Angular application, you’ll dynamically load the React remote’sremoteEntry.jsand then render the exposed React component. One common approach is to use a wrapper component or simply create a containerdivwhere the React app can be mounted.
You’ll first add the React remote to your Angular shell’smodule-federation.config.js:
module.exports = withModuleFederationPlugin({
remotes: {
"react_remote": "http://localhost:4201/remoteEntry.js, // Adjust port if needed
},
// ...other congigurations
});
Then, in an Angular component, you can dynamically load and mount the React component. This typically involves usingSystem.jsor similar dynamic import mechanisms and rendering the React component within an Angular template.
// In an Angular component's .ts file
import {Component, OnInit, ViewChild, ElementRef} from '@angular/core';
@Component({
selector: 'app-react-container',
template: '<div #reactRoot></div>',
standalone: true // If using Angular 17+
})
export class ReactContainerComponent implements OnInit {
@ViewChild('reactRoot', {static: true}) reactRoot: ElementRef;
async ngOnInit() {
const {mount} = await import ('react_remote/Counter' as any); // Assuming Counter is exposed
mount(this.reactRoot.nativeElement);
}
}
(Note: The exact implementation for mounting React in Angular might vary slightly depending on your setup and if you’re using React as a custom element.) - Data communication between apps:
Developers can employ several patterns for data exchange between microfrontends:- Props/Attributes: Use simple data passing when the host renders a component.
- Custom Events: Microfrontends can dispatch custom events that other microfrontends can listen to.
- Shared State Libraries: A shared state management solution (like a tiny publish-subscribe library or even a custom event bus) can facilitate more complex communication.
- URL Parameters/Query Strings: For routing-related data.
Deployment Strategy
Deploying microfrontends requires a thoughtful approach to ensure independence while maintaining a cohesive user experience.
- Deployment options: mono-repo vs multi-repo:
- Mono-repo: All microfrontend codebases reside in a single repository. This can simplify shared tooling and dependency management but might lead to larger CI/CD pipelines.
- Multi-repo: Each microfrontend has its own repository. This offers maximum autonomy and clear ownership but requires careful management of shared dependencies and communication. For Module Federation, a multi-repo approach is often more aligned with the independent deployment philosophy.
- CI/CD pipeline overview (GitHub Actions, GitLab CI, etc.): Each microfrontend (including the host) should have its own CI/CD pipeline. The pipeline should activate the microfrontends when a change is pushed to its repository:
1. Build the application.
2. Run tests.
3. Generate theremoteEntry.js(for remotes).
4. Deploy the built artifacts to a static hosting service.
The Angular host’s pipeline will deploy its own bundle, and its configuration will point to the deployed URLs of the remote applications. - Environment configs and dynamic URLs:
When deploying, ensure yourmodule-federation.config.js(or equivalent) for the host app uses dynamic URLs for remotes based on the deployment environment (e.g., development, staging, production). This can be achieved through environment variables. - Hosting setup: where to deploy host and remotes:
Services like the following can host both the Angular host and React remote applications as static assets:- Netlify: Excellent for static site hosting with built-in CI/CD.
- Vercel: Similar to Netlify, offering fast deployments and serverless functions.
- AWS S3 + CloudFront: For highly scalable and performant content delivery.
- Github Pages: Simple for smaller projects.
Deploy each microfrontend independently, and configure the host app to reference their publicly accessible remote entry point URLs.
Troubleshooting & Best Practices
Deploying a microfrontend architecture can introduce new challenges. Here are some common issues and best practices:
- Common integration issues:
- Version Mismatches: Ensure shared dependencies (like React, Angular, Webpack) have compatible versions across all microfrontends.
- CSS Conflicts: Use CSS Modules, scoped CSS, or naming conventions to prevent styles from one microfrontend from bleeding into another.
- Global Variable Clashes: Be mindful of global variables and try to encapsulate your microfrontends as much as possible.
- Routing Conflicts: Carefully design your routing strategy, potentially using a central router in the host that delegates to child routers in remotes.
- Version compatibility pitfalls:
Module Federation’ssharedconfiguration is crucial. Usesingleton: truefor libraries that should only be loaded once (e.g., React, Angular core) andstrictVersion: trueto enforce exact version matching, preventing unexpected behavior. - Performance tuning tips:
- Lazy Loading: Always lazy load your microfrontends to reduce initial bundle size and improve load times.
- Caching: Leverage browser caching and CDN caching for
remoteEntry.jsand other static assets. - Bundle Analysis: Use Webpack Bundle Analyzer to identify large dependencies and optimize bundles.
- Dependency Sharing Optimization: Ensure shared dependencies are truly shared and not duplicated across bundles.
- Security considerations:
- CORS: Make sure your hosting environment allows cross-origin requests so the host and remote applications can communicate.
- Content Security Policy (CSP): Ensure a robust CSP is in place to mitigate XSS attacks and control resource loading.
- Vulnerability Scanning: Regularly scan your dependencies for known vulnerabilities.
Key Takeaways
- Microfrontends allow you to combine Angular and React like a dream team.
- With Module Federation, you can load remote components easily and efficiently.
- Use smart deployment strategies to scale safety and independently.
- Keep things modular, and your future self (and team) will thank you!
So, that was a lot, you made it. If you’ve got questions or want to know more about the topic please follow our LinkedIn page Frontend Competency.