Module Federation in Vue Apps: Shared Dependencies
AI generated
<v/>
{ }
Vue.js · Module Federation · Vite · Runtime
Module Federation in Vue Apps
Shared dependencies and runtime integration without a rebuild

Module federation loads Vue components in the browser from separately deployed applications, instead of bundling them at build time. This enables independent deployments across team boundaries, but requires a deliberate strategy for shared dependencies, version conflicts and fallbacks when a remote is unreachable.

18 min read Host · remote · shared deps · fallback Vue 3 · vite-plugin-federation · Webpack 5

1. What module federation means for Vue apps

Module federation is a bundler feature, originally from Webpack 5, now also available via vite-plugin-federation for Vite projects, that allows loading JavaScript modules at runtime from a different, independently built application. For Vue apps this means concretely: a host application can import a Vue component from a completely separately deployed remote application as if it were a local module, without the host application depending on the remote codebase at build time.

The crucial difference from classic npm packages is the point in time the binding happens. An npm package is embedded firmly into the host bundle at build time, and every change requires a new host build. Module federation in Vue apps resolves this binding only at runtime in the browser, so the remote team can publish a new version, and the host application automatically loads the current version on the next page visit, without anyone rebuilding the host application.

This property makes module federation one of the most popular techniques for micro frontend architectures with Vue, because it combines real deployment independence with the convenience of native JavaScript modules. The cost is additional configuration complexity around shared dependencies, version compatibility and error handling when a remote is unreachable at runtime.

2. Host and remote: the basic configuration

Every module federation configuration distinguishes between host and remote. The host is the application that consumes foreign modules, the remote is the application that provides its own modules via an entry point called remoteEntry.js. A Vue application can take on both roles at once: it consumes modules from one team and provides modules for another team itself.

The basic configuration defines on the remote side which modules are exposed under which name, and on the host side which URL a given remote is reachable at. In module federation with Vue apps, this URL typically points to a CDN address or a storage bucket to which the remote team's CI/CD pipeline uploads a new version on every deployment.


// remote/vite.config.js — Exposes Vue components as federated modules
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    vue(),
    federation({
      name: 'checkout_remote',
      filename: 'remoteEntry.js',
      exposes: {
        './CheckoutSummary': './src/components/CheckoutSummary.vue',
        './useCheckoutState': './src/composables/useCheckoutState.js',
      },
      shared: ['vue', 'vue-router', 'pinia'],
    }),
  ],
  build: {
    target: 'esnext',
    modulePreload: false,
    cssCodeSplit: false,
  },
});

// host/vite.config.js — Consumes the remote module
export default defineConfig({
  plugins: [
    vue(),
    federation({
      name: 'shell_host',
      remotes: {
        checkout_remote: 'https://cdn.mironsoft.de/checkout/remoteEntry.js',
      },
      shared: ['vue', 'vue-router', 'pinia'],
    }),
  ],
});

3. Exposes: sharing Vue components deliberately

The exposes block decides which parts of a Vue module are visible to other applications at all. A proven practice in module federation with Vue is not to expose the entire application, but to deliberately share individual, clearly bounded components and composables. This keeps the public interface of the remote module small and stable, while internal implementation details, for example helper components used only internally, are never exported in the first place.

When importing on the host side, the exposed module is loaded via a dynamic import whose module name is composed of the remote name and the exposed path. Vue then treats the loaded component like any other async component, including the ability to combine it with defineAsyncComponent and a loading state while the network request for the remote chunk is still running.


// host/src/components/CheckoutSlot.vue — Consuming a remote Vue component
import { defineAsyncComponent } from 'vue';

const RemoteCheckoutSummary = defineAsyncComponent({
  loader: () => import('checkout_remote/CheckoutSummary'),
  loadingComponent: CheckoutSkeleton,
  errorComponent: CheckoutFallback,
  delay: 100,
  timeout: 5000,
});

export default {
  components: { RemoteCheckoutSummary },
  template: `<RemoteCheckoutSummary :order-id="orderId" />`,
  props: ['orderId'],
};

4. Configuring shared dependencies correctly

Without explicit configuration, every remote would bring its own copy of Vue, leading to duplicated framework code and, worse, to reactivity bugs, because two different Vue instances cannot share the same component instances and reactivity graphs. The shared block in module federation solves this problem by defining which libraries are shared between host and remote instead of loaded multiple times.

For Vue apps, at least vue, vue-router and, if used, pinia are candidates for the shared configuration. The singleton: true setting is important, forcing exactly one instance to exist at runtime instead of loading several compatible versions in parallel. Without singleton, host and remote may end up using different Vue instances, which leads to subtle bugs around provide/inject or global plugins.


// vite.config.js — Explicit shared dependency configuration with version constraints
federation({
  name: 'shell_host',
  remotes: {
    checkout_remote: 'https://cdn.mironsoft.de/checkout/remoteEntry.js',
  },
  shared: {
    vue: {
      singleton: true,        // exactly one Vue instance across host and remote
      requiredVersion: '^3.4.0',
      strictVersion: false,   // warn instead of hard-fail on mismatch
    },
    'vue-router': {
      singleton: true,
      requiredVersion: '^4.3.0',
    },
    pinia: {
      singleton: true,
      requiredVersion: '^2.1.0',
    },
  },
});

5. Versioning and compatibility contracts

Because host and remote are deployed independently of each other, an organizational problem arises that cannot be fully solved technically: who guarantees that a remote team does not change its exposed Vue component in a way that breaks the host? Module federation in Vue apps therefore needs an explicit compatibility contract between teams, typically in the form of contract tests that run against the current host integration before every remote deployment.

A pragmatic approach is semantic versioning for exposed modules: a breaking change to an exposed Vue component, for example a changed prop signature, requires a new major path in the manifest, so old host versions can keep loading the old remote version, while new host versions deliberately migrate to the new remote version. Without this discipline, host and remote drift apart unnoticed until a production incident makes the incompatibility visible.


#!/usr/bin/env bash
# ci/contract-test-remote.sh — Run before every remote deployment
set -euo pipefail

readonly HOST_STAGING_URL="https://staging.mironsoft.de"
readonly REMOTE_BUILD_DIR="dist"

# Serve the newly built remote locally and point a real host build at it
npx serve "$REMOTE_BUILD_DIR" --listen 4174 &
SERVER_PID=$!
trap 'kill $SERVER_PID' EXIT

# Contract tests run the host's integration test suite against this remote build
npm run test:contract -- --remote-url=http://localhost:4174/remoteEntry.js

echo "[OK] Remote is compatible with the current host integration contract"

6. Fallback strategies for unreachable remotes

A remote that is unreachable at runtime, for example due to a CDN outage or a faulty deployment, must never crash the entire host application. Module federation with Vue therefore requires explicit error handling on the host side: errorComponent on defineAsyncComponent, combined with a timeout so a hanging network request does not block the whole page.

For critical remote modules, such as checkout, a locally bundled fallback version inside the host is additionally recommended, activated only when the remote does not respond within the timeout. This fallback version does not need to be functionally complete, but should at least cover the critical path, such as completing an order, in a simpler form, instead of showing the user a blank page or an error message.

7. TypeScript types across remote boundaries

TypeScript type safety across module federation boundaries is one of the biggest sources of friction in practice, because the host has no idea at build time which props a remote component expects. The common solution is for each remote team to publish a separate .d.ts file with the public types of its exposed modules, either as a small npm package or as a generated file automatically extracted from the Vue components in the CI process.

These type declarations are purely static, included in the host at development time, but do not change the runtime behavior of module federation. This means a host developer gets full autocomplete and type checking in the IDE when importing a remote Vue component, while the actual component is still loaded dynamically at runtime. Without these type declarations, every remote component degrades to any in the host, making prop typos visible only at runtime.

8. Performance: preloading and bundle size

A common misunderstanding about module federation in Vue apps is that shared dependencies automatically solve performance problems. In fact, the browser must trigger an additional network request on the first access to a remote module, even if Vue itself is already loaded. For modules needed on every page, such as a header or navigation, preloading via <link rel="modulepreload"> is worthwhile, starting the additional request already during the initial page build.

The bundle size of every remote should be monitored separately, because a single, carelessly growing remote module can worsen the perceived load time of the entire host application without this being visible in the host bundle itself. Bundle analysis tools such as rollup-plugin-visualizer should therefore run in every remote pipeline, not just in the host, to catch regressions early before they affect users in production.


// host/src/preload.js — Preloading frequently needed remotes during initial page load
export function preloadRemote(remoteEntryUrl) {
  const link = document.createElement('link');
  link.rel = 'modulepreload';
  link.href = remoteEntryUrl;
  document.head.appendChild(link);
}

// Called for remotes needed on nearly every page, e.g. header or navigation
preloadRemote('https://cdn.mironsoft.de/header/remoteEntry.js');

// Bundle size budget enforced in the remote's own CI pipeline
// rollup-plugin-visualizer + a size-limit check, not just in the host

9. Module federation tooling compared

Besides vite-plugin-federation and native Webpack 5 federation, there are further approaches for runtime integration in Vue apps, each making different tradeoffs between maturity, bundler binding and feature scope.

Tool Bundler binding Maturity Distinguishing feature
Webpack 5 module federation Webpack Very high Reference implementation, largest ecosystem
vite-plugin-federation Vite Good Faster dev builds, younger project
Module federation 2.0 Bundler agnostic Growing Runtime API, built in type sync
Native federation (Angular team) None Good Based on import maps, framework agnostic
Custom elements (alternative) None Very high No shared Vue needed, but shadow DOM isolation

For new Vue projects that already use Vite as the build tool, vite-plugin-federation is usually the most pragmatic choice, because it fits seamlessly into the existing Vite configuration. Projects that already use Webpack or need maximum maturity and community support benefit more from the Webpack 5 reference implementation.

Mironsoft

Module federation, micro frontends and Vue runtime architecture

Combine Vue modules safely at runtime?

We configure module federation for your Vue apps, define shared dependency contracts between teams, and build fallback strategies that stay stable even when a remote is disrupted.

Setup

Production ready host and remote configuration with Vite or Webpack

Version contracts

Establishing contract tests and compatibility rules between teams

Resilience

Fallback components and timeout strategies for failed remotes

10. Summary

Module federation in Vue apps solves runtime integration between independently deployed applications by combining JavaScript modules only in the browser instead of wiring them together firmly at build time. Configuring host, remote and exposed modules is the easy part. The real challenge lies in shared dependencies with singleton: true, explicit version contracts between teams and robust fallback strategies for when a remote is unreachable.

TypeScript type safety across remote boundaries requires additional tooling discipline, such as published type declarations per remote. On the performance side, preloading pays off for frequently needed modules, along with separate bundle analysis per remote. Those who implement these building blocks cleanly get, with module federation, one of the most flexible techniques for scalable Vue architectures with multiple independent teams.

Module Federation in Vue Apps — the essentials at a glance

Host & remote

Host consumes, remote exposes via remoteEntry.js. Both roles are combinable.

Shared dependencies

singleton: true for Vue, router and Pinia prevents duplicated instances.

Version contracts

Contract tests between teams prevent breaking changes to exposed modules.

Resilience

errorComponent, timeout and a local fallback version for failed remotes.

11. FAQ: Module Federation in Vue Apps

1What is module federation in Vue?
Loads Vue components at runtime from an independently deployed application instead of bundling them at build time.
2Why set singleton: true?
Prevents multiple parallel loaded Vue instances and the resulting reactivity bugs.
3TypeScript types across remotes?
Separate .d.ts files per remote, as an npm package or generated in the CI process.
4What if a remote is unreachable?
errorComponent, timeout and optionally a local fallback version for critical modules.
5vite-plugin-federation or Webpack 5?
Vite plugin for existing Vite projects, Webpack 5 for maximum maturity and ecosystem support.
6Preventing breaking changes?
Semantic versioning of exposed modules plus contract tests against the current host integration.
7Does this hurt load time?
First access costs an extra request. Preloading for frequently needed modules reduces the effect.
8Host also a remote at once?
Yes, a Vue application can consume and expose its own modules at the same time.
9Which dependencies to share?
At least vue, vue-router and pinia. Large UI frameworks can also be added to the shared block.
10Worthwhile for small projects?
Rarely. Usually only pays off with several independent teams with their own deployment cycles.