Microfrontends
Microfrontends: Isolation & Parallel Feature Development
1. Why microfrontends
In traditional orgs:
- Many teams, different cadences → each owns a slice (dashboard, billing, analytics) and deploys independently.
- Fewer cross‑team bottlenecks; smaller blast radius when something breaks.
In LLM‑assisted development:
- Usually not many human teams, but there is a need to freeze working areas while agents generate or refactor others.
- Parallel feature work by separate agents/models is safer when each works in a bounded remote with a public API and stable contracts.
- Finished pieces can be versioned and reused; unfinished ones do not block the rest of the app.
The goal: decouple ownership and deployment so that parts can evolve independently while the user sees one cohesive product.
2. Composition models (pick by granularity)
A. Route‑based composition
- Each microfrontend owns entire routes (e.g.,
/billing
,/analytics
). - Works well with platform support (e.g., provider routing or edge rewrites).
- Pros: simple mental model; clean ownership boundaries; good for SEO pages.
- Cons: hard to share small widgets across routes; slower to compose fine‑grained UIs.
B. Runtime federation (module federation)
- A host shell can load components or modules from remote builds at runtime:
import('remoteApp/Widget')
. - Typical tooling: Webpack Module Federation or Vite + vite‑plugin‑federation.
- Pros: fine‑grained reuse (cards, dialogs, flows); true independent deploys; great for parallel agent work.
- Cons: more moving parts; careful versioning for shared deps; SSR can be tricky—prefer client‑side mounting for federated parts.
Rule of thumb:
- Whole sections/pages owned separately → route‑based.
- Shared components/flows across pages or agent‑parallelism per feature → runtime federation.
3. Minimal architecture
- MainContainerUI: global router, auth/session, navigation, error boundaries, design tokens/themes.
- Remotes (features): self‑contained apps for domains (e.g.,
profile
,billing
,analytics
). Each exposes a mountable entry (page or widget) and a small public API. - Shared libraries: design system (e.g., shared‑ui‑elements), types, and small utilities. Treat as versioned packages; avoid ad‑hoc cross‑imports.
- Contracts at the seams: define what a remote exports (components, types, events). Changes are additive; breaking changes require a major bump.
- Observability: log
remoteName@version
in errors and analytics; add health pings per remote.
4. Workflow with agents/models
- One remote = one work unit. Assign an agent to a remote with a clear brief and public API; disallow edits outside its boundary.
- Freeze finished remotes. Mark read‑only; agents can depend on them but not modify internals.
- Contract tests first. For each remote export, keep tiny tests the host runs to verify props/events. Agents target these tests.
- Integration gates. Host runs smoke tests that mount each remote. Only green remotes are linked in the manifest.
- Prompts that enforce boundaries.
- “Use only documented exports from
profile/remote
. Do not import internal paths. If an export is missing, propose an additive change instead of editing internals.”
5. Setup (runtime federation with Vite)
Assumptions: React, TypeScript, Vite,
vite‑plugin‑federation
.5.1 Host
- Owns router, auth, layout, error boundaries.
- Loads remotes via a remote manifest (URLs per environment).
- Shares core deps (react, react‑dom) to avoid duplication.
vite.config.ts (host)
ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from '@originjs/vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'host',
remotes: {
profile: 'http://localhost:5001/assets/remoteEntry.js',
billing: 'http://localhost:5002/assets/remoteEntry.js'
},
shared: ['react', 'react-dom']
})
]
});
5.2 Remote (e.g., profile
)
- Exposes mountable components and a small API.
- Declares the same shared deps.
vite.config.ts (remote)
ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from '@originjs/vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'profile',
filename: 'remoteEntry.js',
exposes: {
'./Page': './src/ProfilePage.tsx',
'./Widget': './src/ProfileCard.tsx'
},
shared: ['react', 'react-dom']
})
]
});
Host usage
tsx
const ProfileCard = React.lazy(() => import('profile/Widget'));
5.3 Dev & prod
- Dev: run each remote on its own port; the host pulls from local URLs.
- Prod: publish each remote to its own origin (immutable build); host reads a manifest with versioned URLs; roll back by swapping the manifest.
6. Data, auth, and safety
- Auth/session: keep in the host; pass a short‑lived token/context down to remotes. Avoid direct storage access from remotes.
- API access: call through a small adapter package so remotes share the same client behavior and error shapes.
- Permissions: hide routes and widgets the user is not allowed to see at the host level.
- Error boundaries around each remote; a failing remote should degrade gracefully without breaking the shell.
- Security: load remotes over HTTPS; restrict allowed origins; consider Subresource Integrity (SRI) for remote entries.
7. Performance & DX
- Bundle sharing: pin versions of shared deps; avoid multiple React copies.
- Prefetch & lazy: prefetch likely remotes; lazy‑load the rest; show skeletons.
- Caching: long cache for immutable remote builds; short cache for the manifest.
- Testing: keep per‑remote unit tests and host‑level smoke tests that mount each remote.
8. When not to use microfrontends
- Very small apps or single‑page widgets where a single bundle is simpler.
- Heavy server‑side rendering/SEO on every component; consider route‑based segmentation instead of runtime federation.
- Teams uncomfortable with versioning at the edges; start with a monolith and split later.
9. Takeaways
- Microfrontends decouple ownership and deployment.
- For LLM/agent workflows, they provide safe isolation so parallel work does not collide.
- Choose route‑based for whole sections, runtime federation for shared widgets and parallel development.
- Keep host simple (auth, layout, routing); keep remotes bounded with clear contracts; version and observe everything.