Skip to content

Ways to Create a Seamless Application for Multiple Standalone Microfrontends

As modern web applications scale, many teams are turning to microfrontend architecture to split large apps into smaller, manageable, and independently deployable units. However, serving standalone apps (like with NGINX) is only half the battle—making them work seamlessly together as one cohesive user experience requires thoughtful integration.

This guide explores the top strategies to stitch together multiple microfrontends into a single, unified application.


1. Single-SPA: The Microfrontend Orchestrator

Single-SPA is a powerful JavaScript framework for orchestrating multiple frameworks or libraries as microfrontends.

🛠 How It Works:

  • You build each app independently (e.g., /app1, /app2).
  • A root-config app manages which one to load based on the route.
  • Uses SystemJS or native import() to load apps dynamically.

Example:

js
registerApplication({
  name: "@org/app1",
  app: () => System.import("@org/app1"),
  activeWhen: ["/app1"],
});

registerApplication({
  name: "@org/app2",
  app: () => System.import("@org/app2"),
  activeWhen: ["/app2"],
});

start();

Pros:

  • Route-based activation
  • Supports multiple frameworks (React, Vue, Angular)
  • Rich ecosystem and documentation

2. Module Federation (Webpack 5)

Module Federation allows apps to share code and load each other at runtime.

🛠 How It Works:

  • Apps expose components via ModuleFederationPlugin.
  • Other apps consume them as remotes.
  • Great for sharing dependencies like React, styles, and state.

Example:

App1 Webpack Config

js
plugins: [
  new ModuleFederationPlugin({
    name: "app1",
    filename: "remoteEntry.js",
    exposes: {
      "./App": "./src/App",
    },
    shared: ["react", "react-dom"],
  }),
];

Pros:

  • Shared dependencies = faster load
  • Dynamic import = performance benefits
  • Works well with CI/CD and versioning

3. Custom Router + JavaScript Runtime Integration

You can manually load microfrontend apps using <script> tags and mount them into specific DOM nodes.

Example:

html
<div id="app1-root"></div>
<script src="/app1/main.js"></script>
<script>
  window.App1.mount(document.getElementById("app1-root"));
</script>

Pros:

  • Simple and flexible
  • No build tool coupling

Cons:

  • You must define a global mount() function
  • Managing shared state and dependencies gets tricky

4. iframe-based Isolation (Optional for Admin Tools or Legacy Apps)

Embedding microfrontends inside <iframe>s is the most isolated approach.

html
<iframe src="/app1" width="100%" height="800px"></iframe>

Pros:

  • Full isolation (CSS, JS, state)
  • Safe for untrusted apps

Cons:

  • Navigation and deep linking are hard
  • Poor performance and UX
  • Not ideal for seamless integration

5. Import Maps and CDN-shared Libraries

You can serve shared dependencies (like React, Vue, etc.) via a CDN or importmap, reducing duplication and ensuring consistency.

HTML Example:

html
<script type="importmap">
  {
    "imports": {
      "react": "https://cdn.skypack.dev/react",
      "react-dom": "https://cdn.skypack.dev/react-dom"
    }
  }
</script>

Pros:

  • Smaller bundles
  • Shared cache across apps

NGINX Setup: Serving Standalone Microfrontends

If you’re serving apps like /app1, /app2, etc., a typical nginx.conf might look like:

nginx
location = /app1 {
  return 301 https://$host/app1/;
}
location /app1/ {
  try_files $uri $uri/ /app1/index.html;
}
location /app1/assets/ {
  alias /var/www/apps/app1/assets/;
}

For seamless routing across apps, use a fallback rewrite rule:

nginx
location / {
  try_files $uri $uri/ @microfrontends;
}

location @microfrontends {
  rewrite ^/app1(/.*)?$ /app1/index.html break;
  rewrite ^/app2(/.*)?$ /app2/index.html break;
}

Best Practices for Seamless Integration

AreaRecommendation
RoutingCentralize routing in a root-config app
State SharingUse federated state management (Redux, Zustand)
StylingNamespace CSS or use Shadow DOM
AuthShared authentication and session management
NavigationProvide a global navigation shell or menu

Conclusion

Serving multiple standalone microfrontends is just the first step. To make them feel like one application, you need:

  • A shared orchestration layer
  • Consistent routing and state
  • Smooth user transitions

By using tools like Single-SPA or Webpack Module Federation, and proper server configurations, you can build a powerful, scalable microfrontend architecture that feels like a single product to your users.