Micro-Frontends in Mobile Development: Architecture, Benefits, and Implementation Challenges

Most apps start small. A single team builds a single codebase, ships it, and everything is simple. Then the product grows. New features arrive, more developers join, and the codebase becomes one large piece that everyone touches. A small change in one area needs a full rebuild and a full release. Two teams edit the same files and block each other. A bug in a minor feature takes down the whole screen.Backend teams faced this years ago and answered with microservices. Micro-frontends apply the same idea to the user interface. In this guide you will learn what micro-frontends are, when they help and when they hurt, how to build one on the web with React and Webpack Module Federation, how the same thinking applies to mobile apps (with a Flutter example), and how to avoid the most common mistakes.

What is a micro-frontend?

A micro-frontend is an independently built and owned piece of a larger user interface. You can think of it as a microservice that lives in the browser (or in the app): it has its own codebase, its own team, and, ideally, its own build and deployment pipeline. Several of these pieces are then composed into one seamless experience for the user.

A typical e-commerce page shows the idea well. On a single product page you might have:

  • A product team that owns the product details and gallery
  • A checkout team that owns the basket and the buy button
  • A recommendations team that owns the “you may also like” section
  • A search team that owns the search bar and results

The user sees one page. Behind it, four teams work on four codebases, each with its own mission. Each team can build, test, and release its part without waiting for the others.

Monolithic frontend vs micro-frontends

In a monolithic frontend, everything (accounts, credit, payments, offers, and so on) lives inside a single application. It is straightforward at first, but it brings two well-known problems as the app grows:

  • One failure can bring everything down. If one feature crashes, the whole application can be affected.
  • Teams block each other. If the credit feature depends on code from the accounts feature, the developer working on credit may have to wait until the accounts developer finishes.

With micro-frontends, each feature is its own application. The accounts team and the credit team work independently. If the credit part breaks, the rest of the site can keep working, provided you have added sensible fallbacks.

Monolithic frontend Micro-frontends
Codebase One large codebase Several smaller codebases
Team workflow Teams share code and often wait for each other Teams work independently on their own parts
Deployment Everything is released together Each part can be released on its own
Failure impact A single failure can affect the whole app Failures can be isolated to one part
Technology One framework for everything Different frameworks are possible (with trade-offs)
Complexity Simple to start, harder as it grows More setup up front, easier to scale teams

Benefits

  • Scalability across teams. Split the product into business domains (products, cart, transactions) and give each to a team that can move at its own pace.
  • Reusability. A micro-frontend, such as a product widget or a login form, can be reused in other applications.
  • Reliability. If one micro-frontend fails to load, the rest of the application can keep working. This does not happen automatically. You need fallbacks and error handling, which we cover below.
  • Technology flexibility. One team can use React while another uses a different framework. This is possible, but mixing frameworks adds weight and complexity, so many companies deliberately standardise on one.
  • Independent pipelines. Each part can have its own CI/CD, tests, and deployment process, which shortens release cycles.

Drawbacks and challenges

  • Higher complexity and cost. More repositories, more pipelines, and more infrastructure. For a simple application that is not complex, this is extra overhead with little return.
  • Hidden coupling. If micro-frontends depend heavily on each other, you get a distributed monolith: all the complexity of separate apps with none of the independence.
  • Duplicate dependencies and larger bundles. If every micro-frontend ships its own copy of React and other libraries, users download more code. Shared dependencies must be configured carefully.
  • Consistent user experience. Separate teams can drift apart visually. A shared design system is close to essential.
  • Shared state and communication. Deciding how parts talk to each other (login state, cart contents, events) needs deliberate design.
  • Testing and monitoring. Each part can pass its own tests, yet the combined result can still break. Integration testing and tracing across parts matter.

When should you use micro-frontends?

Micro-frontends solve an organisational problem more than a technical one. They make the most sense when:

  • Several teams work on one product and keep getting in each other’s way
  • The application is large and can be split along clear business domains
  • Different parts need to be released on different schedules
  • You are gradually replacing an old application piece by piece

When to skip them: a small team, a small or medium application, or a product where everything changes together. A well-organised single codebase, or a modular monolith, is simpler, cheaper, and faster to work with.

Ways to build micro-frontends

There is no single way to do this. These are the most common approaches:

Approach How it works Trade-off
iframes Each part runs in its own embedded page Strong isolation, but awkward for layout, routing, and shared state
Web Components Each part is a custom element loaded on the page Framework-neutral, but needs extra work for shared dependencies
Shared npm packages Parts are published as packages and combined at build time Simple, but every change needs a rebuild and redeploy of the host
Module Federation Parts are loaded at runtime from separately deployed builds True independent deployment, but needs careful version and dependency management

The rest of this guide focuses on Webpack Module Federation, which is the most popular runtime approach for React applications.

Hands-on: React and Webpack Module Federation

We will build two small React applications. app1 is the remote: it exposes a Button component. app2 is the host: it loads and displays that button at runtime, without bundling it.

Tooling changes quickly. This example uses plain Webpack 5, which includes Module Federation built in. Newer options such as Module Federation 2 and Rspack or Vite plugins exist. The core ideas (names, exposes, remotes, shared) stay the same. Check the official documentation for the latest setup steps.

Step 1: Project layout

mfe-demo/
  app1/     (remote: exposes Button, runs on port 3001)
  app2/     (host: consumes Button, runs on port 3002)

Inside each folder, run:

npm init -y
npm install react react-dom
npm install -D webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader @babel/core @babel/preset-react

Add a start script to each package.json:

"scripts": {
  "start": "webpack serve"
}

Step 2: Build app1 (the remote)

app1/public/index.html
<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
  </body>
</html>
app1/src/Button.js
import React from "react";

export default function Button() {
  return <button>Button from App 1</button>;
}
app1/src/App.js
import React from "react";
import Button from "./Button";

export default function App() {
  return (
    <div>
      <h1>App 1</h1>
      <Button />
    </div>
  );
}
app1/src/bootstrap.js
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

createRoot(document.getElementById("root")).render(<App />);
app1/src/index.js
// Load the app asynchronously so shared modules can be negotiated first
import("./bootstrap");

Now the Webpack configuration, including the Module Federation plugin:

app1/webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  devServer: {
    port: 3001,
  },
  output: {
    publicPath: "auto",
  },
  resolve: {
    extensions: [".js", ".jsx"],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: ["@babel/preset-react"],
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "app1",
      filename: "remoteEntry.js",
      exposes: {
        "./Button": "./src/Button",
      },
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true },
      },
    }),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),
  ],
};

What the Module Federation options mean:

  • name: the unique name of this micro-frontend. Other apps use it to refer to this one. It is case sensitive.
  • filename: the manifest file Webpack generates (remoteEntry.js). It lists everything this app makes available to others.
  • exposes: the modules this app shares with the outside world, mapped from a public name (./Button) to a file path.
  • shared: libraries that should be loaded only once across all micro-frontends. Marking React as a singleton prevents two copies of React from running on the same page.

Run it with npm start and open http://localhost:3001. You should see App 1 with its button. You can also open http://localhost:3001/remoteEntry.js to see the generated manifest.

Step 3: Build app2 (the host)

Create app2 with the same files (public/index.html, src/index.js, src/bootstrap.js, and the same Babel rule), then change the following.

app2/webpack.config.js (only the parts that differ)
devServer: {
  port: 3002,
},

// ...

new ModuleFederationPlugin({
  name: "app2",
  remotes: {
    app1: "app1@http://localhost:3001/remoteEntry.js",
  },
  shared: {
    react: { singleton: true },
    "react-dom": { singleton: true },
  },
}),

Here remotes tells app2 where to find app1. The format is name@url-to-remoteEntry. The key on the left (app1) is the prefix you will use in your imports. app2 exposes nothing, so it has no exposes section.

app2/src/App.js
import React, { Suspense } from "react";

// Loaded at runtime from app1
const Button = React.lazy(() => import("app1/Button"));

export default function App() {
  return (
    <div>
      <h1>App 2</h1>
      <Suspense fallback={<p>Loading...</p>}>
        <Button />
      </Suspense>
    </div>
  );
}

Using React.lazy with Suspense is the usual way to render a remote component, because it arrives asynchronously over the network. The remote component must use export default for this to work.

Step 4: Run and test

  1. Start app1 first (npm start in the app1 folder). The host needs the remote to be available.
  2. Start app2 and open http://localhost:3002. You will see the button that comes from app1, rendered inside app2.
  3. Now open app1/src/Button.js and change the text, for example to “Button v2”. Refresh the app2 page. The new text appears, and you did not rebuild or redeploy app2.

That last step is the whole point of runtime composition. When the page loads, app2 fetches remoteEntry.js from app1, so it always gets the latest deployed version of the remote.

Why the bootstrap file matters

You may have wondered why index.js only contains import("./bootstrap"). Module Federation has to negotiate shared libraries (like React) between apps before any of your code runs. If your entry file imports React directly, it tries to use it immediately, before that negotiation finishes, and you get the error “Shared module is not available for eager consumption”. Loading the real app through a dynamic import() creates an asynchronous boundary that gives Webpack time to set everything up. This is why so many Module Federation projects have both an index and a bootstrap file.

Using TypeScript

With TypeScript, the compiler does not know that app1/Button exists, because it is resolved at runtime. Add a declaration file in the host so imports type-check:

app2/src/remotes.d.ts
declare module "app1/Button";

This is the simplest form and treats the module as untyped. For stronger typing, declare the exact shape of each exposed module, or use tooling that generates types from remotes.

Protect the host from a failing remote

Runtime loading means a remote can be down, slow, or broken. Wrap remote components in an error boundary so a failure shows a friendly message instead of crashing the page:

app2/src/RemoteBoundary.js
import React from "react";

export default class RemoteBoundary extends React.Component {
  state = { failed: false };

  static getDerivedStateFromError() {
    return { failed: true };
  }

  render() {
    if (this.state.failed) {
      return <p>This section is temporarily unavailable.</p>;
    }
    return this.props.children;
  }
}
<RemoteBoundary>
  <Suspense fallback={<p>Loading...</p>}>
    <Button />
  </Suspense>
</RemoteBoundary>

This is what turns the claim “one failure does not break the whole app” from a theory into reality.

Combining several micro-frontends in one container app

A more realistic setup uses a container (also called a shell) that assembles several micro-frontends into one application. Imagine a small learning site with three apps:

  • container: the main app users open. It provides navigation and loads the others.
  • home: a remote that exposes a HomePage component.
  • courses: a remote that exposes a Courses component.

Each of the three has its own webpack.config.js with a Module Federation plugin. The rule is simple:

  • A remote uses name, filename, and exposes.
  • The container uses remotes to list every micro-frontend it consumes.
  • All of them declare the same shared libraries.
home/webpack.config.js (Module Federation part)
new ModuleFederationPlugin({
  name: "home",
  filename: "remoteEntry.js",
  exposes: {
    "./HomePage": "./src/components/HomePage",
  },
  shared: {
    react: { singleton: true },
    "react-dom": { singleton: true },
  },
}),
container/webpack.config.js (Module Federation part)
new ModuleFederationPlugin({
  name: "container",
  remotes: {
    home: "home@http://localhost:3001/remoteEntry.js",
    courses: "courses@http://localhost:3002/remoteEntry.js",
  },
  shared: {
    react: { singleton: true },
    "react-dom": { singleton: true },
  },
}),

Then the container renders both remotes and switches between them with React Router:

container/src/App.js
import React, { Suspense } from "react";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

const HomePage = React.lazy(() => import("home/HomePage"));
const Courses = React.lazy(() => import("courses/Courses"));

export default function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>{" | "}
        <Link to="/courses">Courses</Link>
      </nav>
      <Suspense fallback={<p>Loading...</p>}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/courses" element={<Courses />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Run the two remotes first, then start the container. In the browser, the user only visits the container’s address. Clicking “Courses” shows the courses micro-frontend, and clicking “Home” shows the home one, all under the same address. In development the remotes still run on their own ports in the background. In production each one is deployed to its own location, and the user still sees a single site.

Names must match exactly. The name after remotes (home), the name in the remote’s own config, and the import path (home/HomePage) all have to line up, including capital letters. Most first-time errors come from a mismatch here.

A note on Create React App. Older tutorials add Module Federation to Create React App projects by overriding its hidden Webpack configuration, which needs extra scripts and helper files. Create React App is no longer actively recommended for new projects, and a direct Webpack (or Rspack or Vite) setup like the one above is simpler and easier to maintain.

Micro-frontends in mobile development

The word “frontend” does not only mean web. In a mobile app, the same pain appears: a big codebase, many teams, slow builds, and features that step on each other. Mobile teams solve it with the same core idea, splitting the app into self-contained feature modules, but the details differ in one important way.

Compile-time vs runtime composition

On the web, micro-frontends are usually composed at runtime: the host loads the latest version of each piece when the page opens, so a team can deploy without touching anyone else. On mobile, apps normally go through an app store review, so most “micro-frontend” architectures are compile-time modules: separate packages with clear boundaries that are combined when the app is built. You still get team independence and cleaner code, but not independent live deployment.

Platform Typical approach Composition
Flutter Local packages (one per feature), routes exposed by each module Compile time
Android (native) Gradle multi-module projects, dynamic feature modules Compile time, optional on-demand delivery
iOS (native) Swift packages or separate frameworks Compile time
React Native Monorepo packages, or Module Federation with a tool such as Re.Pack Compile time, or runtime with Module Federation

For React Native, Re.Pack (a Webpack and Rspack based bundler that can replace Metro) brings Module Federation to mobile. It is used to build “super apps”: a main host app that loads separately developed mini apps, sometimes maintained by different teams or companies. This gives true runtime composition on mobile, but Apple and Google both have rules about downloading and running code after an app has been reviewed. Check the current store policies before shipping runtime-loaded modules.

Whichever platform you use, the goals are the same: clear module boundaries, one owner per module, minimal coupling, and a small shared core.

Hands-on: modular Flutter app with go_router

Flutter does not have micro-frontends in the browser sense, but its package system makes a very similar architecture easy. We will build an app with four modules, each in its own package:

  • core: shared code, such as app state
  • login: the login screen
  • dashboard: the home screen after login
  • profile: the profile screen

Each feature module owns its screens and its routes. The main app only assembles them. This lets different developers work on different modules with minimal conflicts, and it keeps coupling low.

Step 1: Create the project and modules

flutter create micro_frontend_app
cd micro_frontend_app
mkdir modules

flutter create --template=package modules/core
flutter create --template=package modules/login
flutter create --template=package modules/dashboard
flutter create --template=package modules/profile

Each command creates a Flutter package containing some sample code. You can delete the sample code and replace it with your own.

Step 2: Register the modules in the main app

Open the main app’s pubspec.yaml and add each module as a path dependency:

pubspec.yaml (main app)
dependencies:
  flutter:
    sdk: flutter
  core:
    path: modules/core
  login:
    path: modules/login
  dashboard:
    path: modules/dashboard
  profile:
    path: modules/profile

Add the routing package to the main app and to each module that defines routes:

flutter pub add go_router
cd modules/login && flutter pub add go_router
cd ../dashboard && flutter pub add go_router
cd ../profile && flutter pub add go_router

Modules that use the shared state also need to depend on core. In modules/login/pubspec.yaml and modules/dashboard/pubspec.yaml:

modules/login/pubspec.yaml (dependencies section)
dependencies:
  flutter:
    sdk: flutter
  # go_router is added here by: flutter pub add go_router
  core:
    path: ../core

A module can only import what it declares in its own pubspec.yaml. If you forget to add a dependency, you will see an import error even though the package exists elsewhere in the project.

Step 3: Shared state in the core module

modules/core/lib/app_state.dart
class AppState {
  static String userId = '';
  static String username = '';
}
modules/core/lib/core.dart
export 'app_state.dart';

This simple static class is enough for a demo. Later in this guide we cover better options for real apps.

Step 4: The login module

modules/login/lib/login_screen.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:core/core.dart';

class LoginScreen extends StatelessWidget {
  const LoginScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            AppState.username = 'Alex';
            context.go('/dashboard');
          },
          child: const Text('Login'),
        ),
      ),
    );
  }
}

Now the module declares its own routes. This is the key idea: the module tells the app which routes it provides, and the app does not need to know anything about its screens.

modules/login/lib/login_routes.dart
import 'package:go_router/go_router.dart';
import 'login_screen.dart';

class LoginRoutes {
  static const String path = '/login';

  static final List<RouteBase> routes = [
    GoRoute(
      path: path,
      name: 'login',
      builder: (context, state) => const LoginScreen(),
    ),
  ];
}
modules/login/lib/login.dart
export 'login_routes.dart';

Step 5: The dashboard and profile modules

The dashboard reads the username from the core module and offers a button that opens the profile screen.

modules/dashboard/lib/dashboard_screen.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:core/core.dart';

class DashboardScreen extends StatelessWidget {
  const DashboardScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Dashboard')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('Welcome, ${AppState.username}'),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () => context.push('/profile'),
              child: const Text('Go to profile'),
            ),
          ],
        ),
      ),
    );
  }
}

Create dashboard_routes.dart and dashboard.dart the same way as for login (path /dashboard, exporting DashboardRoutes.routes). Build the profile module the same way, with a simple ProfileScreen and ProfileRoutes for the path /profile.

Step 6: Assemble everything in the main app

lib/main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:login/login.dart';
import 'package:dashboard/dashboard.dart';
import 'package:profile/profile.dart';

final GoRouter router = GoRouter(
  initialLocation: LoginRoutes.path,
  routes: [
    ...LoginRoutes.routes,
    ...DashboardRoutes.routes,
    ...ProfileRoutes.routes,
  ],
);

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(routerConfig: router);
  }
}

Run the app. You land on the login screen, tap the button, arrive on the dashboard with your username, and can open the profile screen.

Step 7: Add a new module in minutes

The real payoff of this structure is how easily it grows. To add a Settings feature:

  1. Create the package: flutter create --template=package modules/settings
  2. Run flutter pub add go_router inside the new module.
  3. Add a SettingsScreen and a SettingsRoutes class with the path /settings, and export it from settings.dart.
  4. Add the module as a path dependency in the main app’s pubspec.yaml.
  5. Add ...SettingsRoutes.routes to the router in main.dart.
  6. Add a button somewhere (for example an icon in the dashboard app bar) that calls context.push('/settings').

Two practical tips. First, context.go() replaces the navigation stack, while context.push() adds a screen on top so the back button works. Use push when you want users to be able to go back. Second, after adding a new package or route, do a full restart. A hot reload will not pick up new dependencies, and you may see a “page not found” error until you restart.

A limit of this approach in Flutter: all modules end up inside one app, and Dart resolves a single version of each dependency for the whole project. Modules cannot each use a different version of the same package the way separate web micro-frontends can. Treat this as a modular architecture inspired by micro-frontends, not as independently deployable pieces.

Sharing state and communication

The hardest design question in any micro-frontend setup is how parts share information. A few principles help:

  • Share as little as possible. Every shared thing is a form of coupling. Agree on a small, stable set: the current user, the auth token, the theme, and a few events.
  • Prefer events and props over direct imports. On the web, custom browser events, a small event bus, or props passed down from the container keep parts loosely connected.
  • Keep ownership clear. Each piece of data should have one module that owns and updates it.
  • Use a proper state solution in apps. The static AppState in our Flutter demo does not notify widgets when values change. For real apps, put a state management solution (for example Riverpod, Bloc, or Provider) in the shared core module so every feature module reads and reacts to the same state.
  • Version your contracts. If a module exposes a component or route that others depend on, treat its public interface like an API and avoid breaking changes.

Best practices

  • Split by business domain, not by technical layer. “Checkout” and “Search” are good boundaries. “All buttons” or “all API calls” are not.
  • Give each module one owning team. Clear ownership is the main reason to use this architecture.
  • Invest in a shared design system. A common set of components, colours, and spacing keeps the interface consistent.
  • Share libraries carefully. Use singletons for frameworks like React, and agree on compatible versions across teams.
  • Design for failure. Add loading states, error boundaries, and fallbacks for every remote piece.
  • Automate integration testing. Test the assembled application, not just each piece on its own.
  • Monitor the whole experience. Track performance and errors across all modules so you can see problems that appear only in combination.
  • Start small. Extract one well-bounded feature first, learn from it, and then continue.

Common errors and how to fix them

Problem Likely cause Fix
“Shared module is not available for eager consumption” The entry file imports React directly Move the app into bootstrap.js and load it with a dynamic import() from index.js
“Invalid hook call” or two copies of React React is not shared as a singleton Add shared with singleton: true for react and react-dom in every app
Remote module cannot be found Name mismatch, wrong URL, or remote not running Check names and capitalisation in name, remotes, and the import path; confirm the remote’s remoteEntry.js opens in the browser
Remote component does not render Missing export default, or not wrapped in Suspense Export the component as default and wrap the lazy import in Suspense
Old version keeps appearing after a deploy remoteEntry.js is cached Serve remoteEntry.js with no-cache or short-cache headers and use hashed names for other files
Works in development but fails in production Hard-coded localhost URLs Use the real deployed URL of each remote, ideally from configuration
Flutter: “page not found” after adding a module Hot reload does not load new packages or routes Fully restart the app and confirm the route is added to the router
Flutter: cannot import a package inside a module The dependency is missing from that module’s pubspec.yaml Add it to that module, for example with flutter pub add

Frequently asked questions

What is a micro-frontend in simple words?

It is a way of building a large user interface out of smaller, independent applications, each owned by its own team, and combining them so the user sees one product.

Are micro-frontends the same as microservices?

They use the same idea, splitting a large system into independent parts, but micro-frontends apply it to the user interface, while microservices apply it to backend services. They are often used together.

What is Module Federation?

It is a Webpack feature (also supported by newer tools) that lets one application load code from another separately built and deployed application at runtime, while sharing common libraries such as React.

Can I mix React, Angular, and Vue in one app?

Technically yes, but it increases bundle size, complexity, and maintenance work. Most organisations choose one framework and use micro-frontends to split ownership, not to mix technologies.

Are micro-frontends good for small projects?

Usually not. The setup and maintenance cost is real, and a small team gains little from it. A well-structured single codebase is often the better choice.

Can I use micro-frontends in a mobile app?

You can use the same ideas. Flutter, Android, and iOS apps are commonly split into feature modules that are combined at build time. React Native can go further with Module Federation and tools such as Re.Pack, which enable runtime loading, subject to store policies.

Do I need a monorepo?

No. Micro-frontends can live in separate repositories or together in one monorepo. A monorepo makes sharing tooling and code easier, while separate repositories give teams more independence.

How do micro-frontends handle a shared login?

Usually the container app manages authentication and passes the user or token to each micro-frontend through props, shared state, or events, so individual parts do not each implement their own login.

Conclusion

Micro-frontends let large teams build one product as a set of independent parts, with separate codebases, separate releases, and failures that stay contained. On the web, Webpack Module Federation makes runtime composition practical, and in mobile development the same thinking shows up as modular feature packages, as in the Flutter example above.

They are not free. They add complexity, and they only pay off when the product and the organisation are big enough to need them. If you do adopt them, keep boundaries clean, share as little as possible, plan for failure, and start with one small feature. Try the React example first, since it shows the runtime idea clearly, and then apply the same principles to your own web or mobile project.

Leave a Reply

Your email address will not be published. Required fields are marked *