Strategies for large teams and independent deployments
Once multiple teams work on the same Vue application, a monolithic frontend becomes a bottleneck. Micro frontends split a Vue application along domain boundaries, so each team can develop, test and deploy independently without waiting for a shared release train.
Table of contents
- 1. Why micro frontends emerge in Vue projects
- 2. Splitting by domain instead of technical layers
- 3. Integration strategies compared
- 4. Runtime integration with custom elements
- 5. Shared state between micro frontends
- 6. Routing and navigation across boundaries
- 7. Design consistency without tight coupling
- 8. Deployment pipelines per micro frontend
- 9. Strategies in direct comparison
- 10. Summary
- 11. FAQ
1. Why micro frontends emerge in Vue projects
A micro frontend is not a Vue specific technique, but an architecture pattern that splits a large frontend into independently deployable units. In Vue projects, the pressure for this usually only builds up beyond a certain team size: once three or more teams work in the same single page application repository, merge conflicts, mutual release blockers and coupling between modules that have nothing to do with each other all start to grow. A micro frontend with Vue solves exactly this organizational problem, not primarily a technical one.
The key mistake when introducing micro frontends with Vue is treating the split purely as a technical exercise, for example by page or component type. Successful splits follow team and domain boundaries: the team that owns checkout owns the checkout micro frontend end to end, including deployment, monitoring and incident response. This ownership is the real payoff that micro frontends with Vue deliver, regardless of which concrete integration technique is chosen in the end.
It is also important to distinguish this from a plain monorepo with multiple Vue apps. A monorepo can share code and still ship as a single deployment. A genuine micro frontend setup allows one team to deploy on Monday and another team only on Thursday, without the two releases affecting each other. This decoupling of deployment cycles is the core promise against which every micro frontend decision with Vue must be measured.
2. Splitting by domain instead of technical layers
The first practical step for any micro frontend with Vue is splitting along business domains rather than technical layers. Instead of building one micro frontend for "all forms" and one for "all lists", the split follows functional areas: product catalog, cart, checkout, account. Each domain gets its own Vue application with its own router, its own store and its own deployment cycle.
The domain boundaries of a micro frontend ideally align with the same boundaries that already exist as a bounded context on the backend, if a domain driven design structure is in place. That significantly reduces the translation work between frontend and backend teams, because both sides speak the same functional language. A Vue micro frontend for checkout then typically talks to exactly the backend services that functionally belong to checkout, instead of aggregating data across several bounded contexts.
A common mistake in domain splitting for Vue micro frontends is cutting too finely. Every additional micro frontend brings integration overhead: its own build, its own deployment, its own monitoring dashboards. As a rule of thumb, the number of micro frontends should roughly match the number of independent teams, not the number of pages or features. A team of three developers rarely benefits from maintaining five separate micro frontends.
3. Integration strategies compared
For technically combining several Vue applications into one overall interface, there are essentially three established strategies: build time integration via npm packages, server side composition via edge includes, and runtime integration in the browser. Each of these strategies has different effects on deployment independence, performance and complexity, and the choice significantly determines how well the micro frontend setup scales long term.
Build time integration means a Vue module is published as a versioned npm package and included by a host application at build time. This is simple to implement but violates the principle of independent deployments: every change requires a new build of the host application. Runtime integration, on the other hand, loads Vue micro frontends in the browser only when needed, typically via module federation or custom elements, allowing each team to deploy its own bundle independently without rebuilding the host application.
// vite.config.js — Runtime integration via dynamic import of a remote entry
// This host app loads independently deployed Vue micro frontends at runtime
export default defineConfig({
build: {
rollupOptions: {
// Host does NOT bundle the remote — only references it
external: [],
},
},
});
// host/src/loadRemote.js
export async function loadRemoteMicroFrontend(remoteUrl, exposedModule) {
// Each remote publishes a manifest with its own version and entry point
const manifestResponse = await fetch(`${remoteUrl}/manifest.json`);
const manifest = await manifestResponse.json();
const script = document.createElement('script');
script.type = 'module';
script.src = manifest.entries[exposedModule];
document.head.appendChild(script);
return new Promise((resolve, reject) => {
script.onload = () => resolve(window.__microFrontends[exposedModule]);
script.onerror = reject;
});
}
Server side composition is a third path that is especially practical in Nuxt projects: an edge layer, for example via Nuxt server routes or a reverse proxy, assembles HTML fragments of several Vue applications into a single page before it reaches the browser. This improves initial load time compared to pure client side runtime integration, but increases infrastructure complexity because every fragment must be reachable server side.
4. Runtime integration with custom elements
One of the most robust techniques for micro frontends with Vue is compiling individual Vue components into native custom elements via defineCustomElement. The result is a standard web component that can be embedded framework agnostically in any host application, regardless of whether it uses Vue itself, React, or plain HTML. For Vue micro frontends this means each team ships its module as a custom element, and the host application does not need to know anything about the internal implementation.
The advantage over module federation lies in the lower coupling to a specific bundler ecosystem and native browser support without an additional runtime framework. The downside: styles must be deliberately isolated via shadow DOM or CSS variables, and communication between custom elements runs through attributes, properties and custom events instead of direct Vue reactivity.
// checkout-widget.js — Compile a Vue component into a native Custom Element
import { defineCustomElement } from 'vue';
import CheckoutSummary from './CheckoutSummary.vue';
const CheckoutSummaryElement = defineCustomElement(CheckoutSummary, {
shadowRoot: true, // isolate styles from the host page
});
customElements.define('checkout-summary', CheckoutSummaryElement);
// Host application — framework agnostic, no Vue dependency needed
// <checkout-summary order-id="4711" currency="EUR"></checkout-summary>
// Inside CheckoutSummary.vue — communicating outward via native CustomEvent
export default {
props: ['orderId', 'currency'],
emits: ['checkout-completed'],
methods: {
completeCheckout() {
// defineCustomElement maps Vue emits to native CustomEvents automatically
this.$emit('checkout-completed', { orderId: this.orderId });
},
},
};
5. Shared state between micro frontends
Shared state is the hardest challenge in micro frontends with Vue, because every module should ideally remain independently deployable while still needing information such as the logged in user or the cart contents across module boundaries. Sharing a global Pinia instance across all micro frontends contradicts the core principle of independence, because then all modules would have to be compiled against the same store version.
The more robust solution is custom events on the window object, or a minimal, version stable event bus distributed as its own very small package. Each Vue micro frontend subscribes to the events it cares about and publishes its own events without knowing the internal store structure of other modules. For server held state, such as user session or feature flags, a central endpoint that each module queries independently is recommended, instead of synchronizing state client side between modules.
// shared/eventBus.js — Minimal, framework-agnostic contract between micro frontends
// Published as its own tiny versioned package — never the full Pinia store
export const MICRO_FRONTEND_EVENTS = {
CART_UPDATED: 'mf:cart-updated',
USER_LOGGED_IN: 'mf:user-logged-in',
};
export function publishEvent(eventName, detail) {
window.dispatchEvent(new CustomEvent(eventName, { detail }));
}
export function subscribeEvent(eventName, handler) {
window.addEventListener(eventName, handler);
return () => window.removeEventListener(eventName, handler);
}
// Inside the cart micro frontend, after a successful add-to-cart mutation
publishEvent(MICRO_FRONTEND_EVENTS.CART_UPDATED, { itemCount: 3, total: 89.90 });
// Inside the header micro frontend, listening without knowing cart internals
import { onMounted, onUnmounted, ref } from 'vue';
const itemCount = ref(0);
let unsubscribe;
onMounted(() => {
unsubscribe = subscribeEvent(MICRO_FRONTEND_EVENTS.CART_UPDATED, (e) => {
itemCount.value = e.detail.itemCount;
});
});
onUnmounted(() => unsubscribe());
6. Routing and navigation across boundaries
Navigation between multiple independently deployed Vue applications requires a clear decision: either a shell router that orchestrates the individual micro frontends, or full browser navigation between separate pages. The shell router approach preserves the single page application experience, but requires the shell to know which micro frontend is responsible for which route, which again increases coupling between shell and modules.
The more pragmatic option for many Vue micro frontend projects is to accept real page transitions at domain boundaries, for example from the product catalog to checkout, and to use full Vue router reactivity only within a single domain. The perceived performance difference is small with good preloading and a shared design system, while decoupling the router configurations contributes significantly to team autonomy.
// shell/src/router.js — Shell router delegates unknown prefixes to remote micro frontends
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/catalog/:pathMatch(.*)*', component: () => loadRemoteView('catalog') },
{ path: '/checkout/:pathMatch(.*)*', component: () => loadRemoteView('checkout') },
{ path: '/account/:pathMatch(.*)*', component: () => loadRemoteView('account') },
],
});
// Each remote view is loaded lazily and mounted with its own internal router
async function loadRemoteView(domain) {
const { default: RemoteApp } = await import(/* @vite-ignore */ `${domain}/RemoteEntry.js`);
return RemoteApp; // owns its own nested vue-router instance internally
}
export default router;
7. Design consistency without tight coupling
Without countermeasures, independently developed Vue micro frontends drift apart visually, because every team implements its own buttons, spacing and colors. The solution is a shared design system, shipped as a standalone npm package with Vue components and design tokens, included by all micro frontends as a dependency. It is important that this package has its own independent versioning, so micro frontends are not forced to immediately follow every design system update.
CSS isolation is the second building block: scoped styles inside every Vue component, combined with CSS custom properties for theming values such as primary color or font size, prevent a micro frontend from accidentally overwriting another module's styles. With custom element based integration, shadow DOM handles this isolation automatically. With pure runtime integration without shadow DOM, a CSS naming convention with unique prefixes per micro frontend is mandatory.
8. Deployment pipelines per micro frontend
The real business value of micro frontends with Vue only becomes visible in the deployment pipeline: each team runs its own CI/CD pipeline, independent of other teams' pipelines. A typical setup builds the Vue module, runs unit and integration tests, publishes the bundle to a versioned storage bucket or CDN address and then updates an import map or manifest entry that the host application reads at runtime.
A frequently underestimated risk is version compatibility between micro frontends and the shared Vue runtime. If Vue itself is included as a shared dependency via module federation, a compatibility contract must define which major versions of Vue are compatible between host and remote. Without this contract, a module tested in isolation can fail in production against an incompatible shared Vue version, a classic integration risk that can only be reliably caught by contract tests between the pipelines.
#!/usr/bin/env bash
# ci/deploy-micro-frontend.sh — Independent pipeline per Vue micro frontend
set -euo pipefail
readonly MODULE_NAME="checkout"
readonly BUILD_DIR="dist"
readonly BUCKET="s3://mf-modules/${MODULE_NAME}"
readonly VERSION="$(git rev-parse --short HEAD)"
npm run build
npm run test:unit
npm run test:contract -- --against=shared-vue-runtime@3
# Upload the versioned bundle, never overwrite an existing version
aws s3 cp "$BUILD_DIR" "${BUCKET}/${VERSION}" --recursive
# Update the manifest that the shell reads at runtime — atomic, single file
cat > manifest.json <<JSON
{ "module": "${MODULE_NAME}", "version": "${VERSION}", "entry": "${BUCKET}/${VERSION}/remoteEntry.js" }
JSON
aws s3 cp manifest.json "${BUCKET}/manifest.json"
echo "[OK] Deployed ${MODULE_NAME}@${VERSION} independently"
9. Strategies in direct comparison
Choosing the right integration strategy for micro frontends with Vue depends heavily on organizational maturity and the number of teams involved. The following overview compares the common approaches along the criteria deployment independence, performance and operational complexity.
| Strategy | Deployment independence | Performance | Complexity |
|---|---|---|---|
| npm package (build time) | Low | Very good | Low |
| Custom elements | High | Good | Medium |
| Module federation | High | Good, shared deps | High |
| Server side composition | High | Very good (SSR) | High (infra) |
| iframe isolation | Very high | Weak | Low |
For most Vue organizations with three to eight teams, a combination of custom elements for smaller, embedded modules and module federation for larger, standalone areas such as checkout is a practical middle ground. iframe isolation remains reserved for special cases with maximum security requirements, for example embedding third party widgets where complete isolation matters more than seamless integration.
Mironsoft
Vue architecture, micro frontends and scalable frontend teams
Multiple teams, one Vue application, independent releases?
We analyze your existing Vue codebase, cut domain boundaries along your team structure and implement a micro frontend architecture that lets every team deploy independently.
Architecture review
Analysis of your Vue codebase and a proposal for domain splitting
Integration
Introducing custom elements or module federation in production
CI/CD setup
Setting up independent deployment pipelines per micro frontend
10. Summary
Micro frontends with Vue primarily solve an organizational problem: independent teams should be able to deploy independently without blocking each other. Domain splitting should follow team structure and bounded contexts, not technical categories. Custom elements via defineCustomElement offer a framework agnostic, loosely coupled integration option, while module federation suits larger, standalone areas with shared dependencies.
Shared state works most robustly through a minimal, version stable event bus rather than a shared store instance. A central design system package keeps visual consistency between independently developed modules. In the end, the decisive success factor is not the technology but the consistent separation of deployment pipelines, because only that creates the real team autonomy that micro frontends promise.
Micro Frontends with Vue — the essentials at a glance
Domain splitting
Split by business domain and team structure, not by technical layer or page count.
Integration
Custom elements for loose coupling, module federation for larger areas with shared dependencies.
Shared state
Minimal event bus instead of a shared Pinia instance, so modules stay independently versioned.
Deployment
Own CI/CD pipeline per micro frontend, contract tests against the shared Vue runtime.