Back

Microfrontends with Webpack, Vite, and PNPM Workspaces - What to Choose and When?

microfrontends
frontend architecture
webpack
vite
pnpm

Technical analysis of Webpack Module Federation, Vite + Workspaces, and PNPM Workspaces in microfrontend architecture, complemented with communication strategies, state management, and testing.

Image of Orchestra, Symphony and Stage

Microfrontends allow a monolithic frontend application to be split into independent modules. This means that each team can work in isolation on their fragment, as if they were looking at their part of the code reflected on the screen. This approach enables independent deployments and remarkable technological flexibility: for example, a team could rewrite their microfrontend in a new framework without blocking the rest.

However, the choice of architecture is not based solely on compilation speed. Interoperability, deployment, maintenance, and, crucially, the developer experience must be considered. Tools like Vite stand out for very fast development cycles, while Webpack (Module Federation) offers more granular control, which is critical in complex enterprise environments. There is no “silver bullet”: each approach has strengths and limitations depending on the context.


Webpack Module Federation

Webpack Module Federation is an advanced microfrontend solution that loads modules from other applications at runtime. It allows different teams to build and deploy independent UI fragments. For example, a microfrontend can “expose” components (buttons, views) and another can import them dynamically without duplicating common libraries. Module Federation’s shared dependency system prevents package duplication (e.g., shared React) and facilitates lazy loading of resources, improving initial performance. This makes it ideal for large-scale enterprise projects: each module can be published separately and updated without redeploying the entire application.

  • Advantages: Independent deployment, isolated versions, and lazy loading of modules. It also supports mixing technologies when necessary.

  • Disadvantages: Webpack and Module Federation have a steep learning curve. Configuration is complex (plugins, ports, CORS) and it is less agile in development than modern tools. Furthermore, it is a Webpack-centric solution (although plugins exist for other bundlers, they are still less mature). In summary, Webpack MF offers maximum control and scalability in exchange for greater complexity.

// webpack.config.js (host)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      remotes: {
        mfApp: 'mfApp@http://localhost:3001/remoteEntry.js'
      },
      shared: ['react', 'react-dom']
    })
  ]
};
// webpack.config.js (remote)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'mfApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button.jsx'
      },
      shared: ['react', 'react-dom']
    })
  ]
};

Vite + Workspaces (monorepo)

Vite is a modern build tool oriented towards development speed, using ESBuild and native ES modules. It integrates well into a monorepo with workspaces (e.g., Yarn or pnpm), where all microfrontends live in a single repository. This allows local dependencies to be shared without publishing external packages. Vite’s development server is extremely fast, with almost instant hot reloading, which greatly accelerates the development experience. Vite has a growing ecosystem and requires very little configuration to get started, perfect for modern small/medium-scale projects.

  • Advantages: Ultra-fast development cycles and simple configuration. Facilitates sharing internal code (common component libraries) directly via imports in the monorepo. It aligns well with modern frameworks (React, Vue3, Svelte) and codebases that follow ES modules.

  • Disadvantages: Without an additional plugin, there is no dynamic microfrontend: Vite, by default, builds everything as a single composite application. To enable independent deployments, something like @originjs/vite-plugin-federation (a plugin that emulates Module Federation) must be used. Although this plugin exists and works, it is not yet as mature or standard as Webpack’s. In production, a monorepo with Vite usually implies a single build pipeline, which can tie the versions of the fragments together. In summary, Vite + workspaces is ideal when prioritizing development speed and simplicity, sacrificing some deployment independence.

// vite.config.ts (host)
import federation from '@originjs/vite-plugin-federation'

export default {
  plugins: [
    federation({
      remotes: {
        remoteApp: 'http://localhost:5001/assets/remoteEntry.js'
      },
      shared: ['react']
    })
  ]
};
// vite.config.ts (remote)
export default defineConfig({
  plugins: [
    federation({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Widget': './src/components/Widget.tsx'
      },
      shared: ['react']
    })
  ]
});

PNPM Workspaces

PNPM Workspaces offers efficient monorepo management. Unlike classic npm or Yarn, pnpm uses a global store for dependencies: each package is installed only once on disk and linked into the workspaces. This implies great advantages in projects with many microfrontends. For example, in npm v7 each workspace duplicates its dependencies (up to 100 identical packages if there are 100 microapps), but pnpm strictly deduplicates them. As a result, npm can fill gigabytes of node_modules, while pnpm only stores one copy per version. Hotjar found that migrating from npm to pnpm in their monorepo drastically reduced installation time and disk space. Additionally, pnpm provides advanced tools (workspace protocols, --filter flags) to execute commands on specific packages, which is useful in highly fragmented repositories.

  • Advantages: Much faster installations and less disk space occupied in node_modules, which improves team productivity. Facilitates version consistency between microfrontends (by pointing to identical dependencies). PNPM workspaces allow local development without packaging or publishing common components.

  • Disadvantages: As with any monorepo, deployment tends to be coordinated (unless combined with an independent bundling system). PNPM solves installation, but does not add runtime magic: microfrontends would still need to be built and served (which may imply a single global bundle if federation is not used). In summary, pnpm is excellent for the development workflow and maintenance of large repositories, but deployment independence depends on how builds are orchestrated.

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
// package.json (root)
{
  "name": "monorepo",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ]
}

When to Choose Each Approach?

When deciding, it is advisable to reflect based on the project’s objectives and limitations:

  • Development cycles: Vite (and workspaces) wins in speed and development experience. Its HMR server is much faster than Webpack and allows for iteration without waiting. Webpack requires slower builds, but offers advanced optimizations in production.
  • Independent deployment: Webpack Module Federation excels here. It allows updating a microfrontend without redeploying the host or other fragments. In a monorepo (Vite or pnpm), everything is usually built together or packages are deployed at the same time, which requires coordination.
  • Complexity and customization: If you need very specific transformations, Webpack supports it due to its mature ecosystem. Vite offers less “plumbing” by default, ideal for simple setups. PNPM itself does not influence the bundler configuration, but it facilitates an organized workspace.
  • Technological interoperability: Module Federation theoretically allows mixing different frameworks or libraries at runtime. In a standard monorepo, a common stack (React, for example) is usually chosen for everything.
  • Resources and infrastructure: Monorepos with pnpm are light on disk, a crucial factor in extensive CI/CD. They also simplify dependency installation and single versioning. MF can involve multiple repositories or separate pipelines, with operational overhead.

These points vary according to the real context: team size, scalability needs, and developers’ technological familiarity. For example, a small startup might prioritize Vite and a monorepo to maximize speed and flexibility in early stages. A large company with several autonomous teams might prefer Module Federation to isolate deployments and architectures between business domains.

In addition to these technical factors, the developer experience is key. Vite stands out for its ultra-fast local server and almost instant reloading, which greatly improves team productivity. Webpack/WebPack MF, on the other hand, provides more complex optimization options, but with a steeper learning curve. Workspace tools like pnpm optimize the daily workflow: they do not duplicate libraries in node_modules, speeding up installations. These details directly impact how it feels to work on the project day by day.


Deep Dive into Orchestration and Developer Experience 🎭

The choice of tools like Webpack Module Federation, Vite, or PNPM Workspaces is fundamental for the development and deployment of microfrontends. However, the long-term success of a microfrontend architecture also depends on how communication, shared state, testing, and overall consistency are managed.

1. Communication Strategies Between Microfrontends 💬

Beyond the direct import allowed by Module Federation, microfrontends often need to communicate in more complex ways. This is crucial for maintaining deployment independence without excessively coupling modules:

  • Publish/Subscribe (Pub/Sub) or Event Bus: A common pattern is to use a global Event Bus (either with libraries like mitt or tiny-emitter, or even with the native CustomEvents API of the browser). Microfrontends publish generic events (e.g., user-logged-in, product-added-to-cart), and other interested microfrontends subscribe to them. This promotes decoupling by not directly depending on the internal APIs of other modules.
  • Shared APIs: For more direct functionalities, a microfrontend can expose a well-defined API (via an SDK or a common module) for others to consume. This is useful when there is a closer relationship between components.
  • URL/Routes: Communication through URL changes is a simple way to navigate between microfrontends. Application-level routing decides which microfrontend is loaded based on the current route.

2. Shared State and Common Data Management 💾

When an application is split, the question arises of how to handle data that is relevant to multiple microfrontends (e.g., logged-in user information, a shopping cart).

  • Centralized Global State (with caution): Although independence is key, sometimes a small “core” or “shell” of the application can manage a minimal global state (e.g., Redux or Zustand in the shell). However, this must be very limited to avoid recreating a state monolith.
  • Browser Storage: localStorage, sessionStorage, or IndexedDB can be used to share non-sensitive data between microfrontends.
  • Web Workers: For complex business logic or data processing that needs to be shared and not block the main thread.
  • Backend Services: The most reliable source for shared data is usually backend services. Microfrontends query data at startup and update it as needed, maintaining the single source of truth on the server.

3. Testing Strategies for Microfrontends 🧪

Testing in a distributed architecture presents unique challenges. It is vital to ensure that microfrontends work in isolation and, more importantly, that they interact correctly:

  • Unit and Integration Testing (Individual): Each microfrontend must have its own suite of unit and integration tests to ensure that its components and logic work correctly in isolation.
  • Contract Testing: Ensures that the APIs exposed by a microfrontend (or the events it emits) comply with the “contract” expected by other microfrontends that consume them. Tools like Pact or Cypress Component Testing with mocks can be useful.
  • End-to-End (E2E) or Flow Testing: Although independence is sought, it is inevitable to perform E2E tests that cover complete user flows across multiple microfrontends to detect integration problems in a simulated or real environment.

4. User Experience (UI/UX) Consistency ✨

Maintaining a consistent UI/UX is a major challenge when multiple teams work on independent fragments.

  • Design Systems: The adoption of a robust and shared Design System (with a common component library) is fundamental. This ensures that all microfrontends use the same styles, fonts, and components (buttons, forms, etc.), offering a unified experience to the end-user.
  • Governance/Coordination Teams: It can be useful to have a small “platform” or “UX/UI” team that establishes guidelines, maintains the Design System, and offers support to microfrontend teams to ensure consistency.

Actionable Conclusion 🎯

Ultimately, there is no silver bullet. The chosen architecture must respond to the needs of the people and the product. Each technology is a means to serve the team. The important thing is to thoughtfully evaluate the technical pros and cons (speed, deployment, maintenance, compatibility) and decide based on the real case, complementing that decision with solid strategies for communication, state management, and consistency between modules.



📚 Consulted Sources