-
Notifications
You must be signed in to change notification settings - Fork 4
Getting Started
This guide walks you through installing and configuring @microfrontend in both a shell application and a microfrontend application.
- Node.js (LTS recommended)
- A SPA framework of your choice (Angular, React, Vue, etc.)
- Hash-based routing enabled in your microfrontend's router
npm install @microfrontend/common @microfrontend/controllernpm install @microfrontend/common @microfrontend/clientThe shell application creates a MetaRouter that manages all microfrontends.
Each microfrontend is identified by a metaRoute name and a baseUrl pointing to where the microfrontend is served:
import { IAppConfig } from '@microfrontend/controller';
const routes: IAppConfig[] = [
{
metaRoute: 'app-a',
baseUrl: 'https://app-a.example.com'
},
{
metaRoute: 'app-b',
baseUrl: 'https://app-b.example.com'
}
];import { MetaRouter, MetaRouterConfig, FrameConfig } from '@microfrontend/controller';
import { Level } from '@microfrontend/common';
const config = new MetaRouterConfig(
'outlet', // outlet name (matches the DOM element id)
routes, // route definitions
(metadata, data) => { // broadcast notification handler
console.log('Broadcast received:', metadata.tag, data);
},
new FrameConfig(), // optional: iframe configuration
UnknownRouteHandlingEnum.ThrowError, // optional: unknown route handling
Level.INFO // optional: log level
);
const router = new MetaRouter(config);The outlet is a plain DOM element where iframes will be injected:
<div id="outlet" style="height: 100%"></div>Important: The
idattribute must match theoutletparameter inMetaRouterConfig.
// Preload creates iframes for all (or specific) routes
await router.preload();
// Initialize starts routing — navigates to the first route or restores from the URL hash
await router.initialize();// Navigate to microfrontend 'app-a'
await router.go('app-a');
// Navigate to microfrontend 'app-a' with subroute 'settings'
await router.go('app-a', 'settings');Each microfrontend uses the RoutedApp class to communicate with the shell.
import { RoutedApp, RoutedAppConfig } from '@microfrontend/client';
import { Level } from '@microfrontend/common';
const config = new RoutedAppConfig(
'app-a', // metaRoute — must match the shell's route config
'https://shell.example.com', // parentOrigin — the shell's origin (for postMessage security)
Level.INFO // optional: log level
);
const routedApp = new RoutedApp(config);Whenever the microfrontend navigates internally, it must report the new route to the shell so the URL hash stays in sync:
// Example with Angular Router
router.events
.pipe(filter(e => e instanceof NavigationEnd))
.subscribe((e: NavigationEnd) => {
routedApp.sendRoute(e.url);
});When the user clicks a navigation link in the shell (or uses back/forward buttons), the shell tells the microfrontend which subroute to display:
routedApp.registerRouteChangeCallback((activated, subRoute) => {
if (subRoute) {
router.navigateByUrl(subRoute, { replaceUrl: true });
} else {
router.navigateByUrl('/', { replaceUrl: true });
}
});Key point: Use
replaceUrl: trueto avoid duplicating history entries — the shell already manages browser history.
if (routedApp.hasShell) {
// Running inside the shell — full microfrontend features available
} else {
// Running standalone — graceful degradation
}Microfrontends must use hash-based routing so that the shell can manage the main URL path while each microfrontend controls its own hash fragment.
RouterModule.forRoot(routes, { useHash: true })import { HashRouter } from 'react-router-dom';
<HashRouter>
<App />
</HashRouter>const router = createRouter({
history: createWebHashHistory(),
routes
});import { Component, OnInit } from '@angular/core';
import { Level } from '@microfrontend/common';
import {
FrameConfig,
IAppConfig,
MetaRouter,
MetaRouterConfig,
UnknownRouteHandlingEnum
} from '@microfrontend/controller';
const routes: IAppConfig[] = [
{ metaRoute: 'a', baseUrl: 'http://localhost:30307' },
{ metaRoute: 'b', baseUrl: 'http://localhost:30809' }
];
@Component({
selector: 'app-root',
template: `
<nav>
<a (click)="go('a')">App A</a> |
<a (click)="go('b')">App B</a> |
<a (click)="go('a', 'settings')">App A — Settings</a>
</nav>
<div id="outlet" style="height: 100%"></div>
`
})
export class AppComponent implements OnInit {
router: MetaRouter;
constructor() {
const config = new MetaRouterConfig(
'outlet',
routes,
(metadata, data) => console.log('Broadcast:', metadata.tag, data),
new FrameConfig({}, {}, { class: 'my-outlet-frame' }),
UnknownRouteHandlingEnum.ThrowError,
Level.LOG
);
this.router = new MetaRouter(config);
}
async ngOnInit(): Promise<void> {
await this.router.preload();
await this.router.initialize();
}
async go(route: string, subRoute?: string): Promise<void> {
await this.router.go(route, subRoute);
}
}// app.tokens.ts
import { InjectionToken } from '@angular/core';
import { RoutedApp } from '@microfrontend/client';
export const ROUTED_APP = new InjectionToken<RoutedApp>('ROUTED_APP');
// app.module.ts
import { RoutedApp, RoutedAppConfig } from '@microfrontend/client';
import { Level } from '@microfrontend/common';
const config = new RoutedAppConfig('a', 'http://localhost:30103', Level.LOG);
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(
[
{ path: 'settings', component: SettingsComponent },
{ path: 'dashboard', component: DashboardComponent },
{ path: '**', redirectTo: 'dashboard' }
],
{ useHash: true }
)
],
providers: [
{ provide: ROUTED_APP, useFactory: () => new RoutedApp(config) }
],
bootstrap: [AppComponent]
})
export class AppModule {}
// app.component.ts
@Component({ selector: 'app-root', templateUrl: './app.component.html' })
export class AppComponent {
constructor(
@Inject(ROUTED_APP) private routedApp: RoutedApp,
private router: Router
) {
// Report route changes to the shell
this.router.events
.pipe(filter(e => e instanceof NavigationEnd))
.subscribe((e: NavigationEnd) => this.routedApp.sendRoute(e.url));
// Handle route changes from the shell
this.routedApp.registerRouteChangeCallback((activated, subRoute) => {
this.router.navigateByUrl(subRoute ?? '/', { replaceUrl: true });
});
// Handle broadcast messages
this.routedApp.registerBroadcastCallback((metadata, data) => {
console.log('Received broadcast:', metadata.tag, data);
});
}
}- Architecture — Understand how the libraries interact
- URL Specification — Learn the hash-based URL format
- Inter-App Communication — Set up broadcast messaging
- State Management — Prevent data loss on navigation
@microfrontend — MIT License — npm — GitHub
Getting Started
Guides
API Reference
Contributing