Shell App Design in Micro Frontends
Learn how to design the shell app in micro frontend architecture, including layout, routing, auth bootstrap, remote loading, feature flags, error boundaries, analytics, and governance.
The shell app is one of the most important parts of a micro frontend architecture.
It is also one of the easiest parts to design badly.
A good shell app gives users one seamless product experience while allowing multiple teams to own and deploy their domain apps independently.
A bad shell app becomes a new frontend monolith.
The most important principle is:
The shell should coordinate composition, not own every domain’s business logic.
This article explains how to design a production-ready shell app for micro frontends, what it should own, what it should not own, and how to explain it in senior frontend interviews.
1. What Is the Shell App?
The shell app is the main container application in a micro frontend system.
It is sometimes called:
Host app
Container app
Platform app
Composition app
Root appIts job is to compose multiple independently owned frontend applications into one user experience.
Example:
Shell App
├── Header
├── Navigation
├── Auth Bootstrap
├── Route Config
├── Remote Loader
├── Error Boundaries
└── Loads:
├── Catalog Remote
├── Cart Remote
├── Checkout Remote
├── Profile Remote
└── Orders RemoteThe shell is the entry point that users load first.
2. Why the Shell Exists
Without a shell, each micro frontend would feel like a separate product.
The shell provides:
One application frame
One navigation model
One authentication bootstrap
One top-level routing system
One remote loading strategy
One global error handling strategy
One consistent user journeyThe shell gives users continuity while teams remain independent internally.
Strong interview phrase:
The shell creates a unified product experience from independently owned frontend domains.
3. High-Level Shell Architecture
┌──────────────────────┐
│ Browser │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Shell App │
│ Layout/Auth/Routing │
└──────────┬───────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Catalog Remote│ │ Cart Remote │ │ Checkout Remote│
└───────────────┘ └───────────────┘ └───────────────┘The shell does not need to own all business logic.
It owns the composition layer.
4. Core Responsibilities of the Shell
The shell usually owns platform-level responsibilities:
Global layout
Top-level routing
Navigation
Authentication bootstrap
Remote loading
Feature flag bootstrap
Global error boundaries
Fallback UI
Analytics initialization
Design system provider
Theme and locale providers
Remote manifest loading
Version awarenessThese are cross-cutting platform concerns.
They are not domain-specific business rules.
5. What the Shell Should Own
5.1 Global Layout
The shell can own:
Header
Footer
Sidebar
Global navigation
Page frame
Global notification areaExample:
Shell Layout
├── Header
├── Main Content Slot
├── Footer
└── Toast RegionThe remote renders inside the content slot.
Shell App
└── Main Content Slot
└── Catalog RemoteThis keeps the user experience consistent.
5.2 Top-Level Routing
The shell should usually own top-level route decisions.
Example:
/ → Home Remote
/categories/:slug → Catalog Remote
/search → Search Remote
/product/:id → Product Details Remote
/cart → Cart Remote
/checkout → Checkout Remote
/profile → Profile Remote
/orders → Orders RemoteThe shell decides which remote should load for each top-level route.
The remote can own nested routes inside its domain.
Profile Remote owns:
/profile/details
/profile/addresses
/profile/preferencesStrong interview phrase:
The shell owns top-level routing, while remotes own domain-level routing.
5.3 Authentication Bootstrap
The shell usually initializes authentication.
It can own:
Login detection
Session bootstrap
Token refresh
Logout handling
Auth provider setup
Global auth guard
Identity contextExample:
Shell starts
│
▼
Check session
│
▼
Load identity summary
│
▼
Render protected route
│
▼
Pass safe identity context to remoteBut the shell should not be the only authorization layer.
Remotes and backend APIs must still enforce feature/domain authorization.
5.4 Remote Loading
The shell loads remotes.
Example:
User opens /cart
│
▼
Shell matches /cart route
│
▼
Shell reads remote manifest
│
▼
Shell loads cart remoteEntry.js
│
▼
Cart Remote rendersThe shell should handle:
Remote URL resolution
Remote version lookup
Loading state
Timeout
Error handling
Fallback UI
Retry if appropriateThis is one of the shell’s most important production responsibilities.
5.5 Error Boundaries
Each remote should be wrapped in an error boundary.
Shell App
├── Catalog Remote
│ └── Error Boundary
├── Cart Remote
│ └── Error Boundary
└── Checkout Remote
└── Error BoundaryIf a remote fails, the shell should not go blank.
Expected behavior:
Cart Remote fails
│
▼
Shell catches error
│
▼
Fallback UI shown
│
▼
Error logged with remote name/version
│
▼
Header and navigation remain usable5.6 Feature Flag Bootstrap
The shell can initialize the feature flag client.
Example:
Shell loads user context
│
▼
Shell initializes feature flags
│
▼
Remote receives relevant flag contextFeature flags may control:
Which remote version is loaded
Whether a new remote is enabled
Whether a new checkout step is shown
Canary rollout percentage
Kill switch behaviorBut domain-specific feature behavior should still live inside the owning remote.
5.7 Global Analytics Initialization
The shell can initialize analytics.
It can own:
Analytics SDK setup
Page view tracking
Route transition tracking
Global user/session fields
Remote version metadataRemotes can emit domain events:
catalog:product-clicked
cart:item-added
checkout:completed
profile:address-updatedThe shell/platform layer can normalize and send them.
This avoids every remote implementing analytics differently.
5.8 Design System Provider
The shell can provide global design system context:
Theme
Color mode
Typography baseline
Locale
Direction
Global CSS reset
Design tokensBut remotes should still import and use the design system correctly.
The shell should not manually style each remote’s domain UI.
6. What the Shell Should Not Own
A shell becomes dangerous when it starts owning domain logic.
The shell should not own:
Product filtering logic
Cart calculation logic
Checkout validation rules
Payment flow logic
Order history business rules
Profile form business logic
Search ranking logic
Promotion rulesBad shell:
Shell App
├── Header
├── Routing
├── Auth
├── Cart calculation
├── Checkout rules
├── Product filters
├── Search business logic
└── Order history logicThis is just a new monolith.
Good shell:
Shell App
├── Layout
├── Routing
├── Auth bootstrap
├── Remote loading
├── Error boundaries
├── Feature flags
└── Analytics bootstrapDomain logic remains inside remotes or backend services.
7. Shell vs Remote Responsibility Table
| Concern | Shell Owns | Remote Owns |
|---|---|---|
| Global layout | Yes | No |
| Top-level routing | Yes | No |
| Nested domain routing | No | Yes |
| Auth bootstrap | Yes | No |
| Feature-level authorization | No | Yes |
| Product filters | No | Catalog Remote |
| Cart business logic | No | Cart Remote |
| Checkout steps | No | Checkout Remote |
| Remote loading | Yes | No |
| Error boundary wrapper | Yes | Remote can add local boundaries too |
| Domain API calls | No | Yes |
| Global analytics setup | Yes | Domain events |
| Design system usage | Provides context | Uses components |
8. Shell Routing Design
A typical route map:
const routeMap = {
"/": "homeApp",
"/categories/:slug": "catalogApp",
"/search": "searchApp",
"/product/:id": "productApp",
"/cart": "cartApp",
"/checkout": "checkoutApp",
"/profile/*": "profileApp",
"/orders/*": "ordersApp"
}The shell maps routes to remotes.
Then the selected remote handles domain UI.
Example:
/profile/addresses
│
▼
Shell matches /profile/*
│
▼
Shell loads Profile Remote
│
▼
Profile Remote renders Addresses pageThis gives predictable ownership.
9. Shell Remote Manifest Design
The shell should not hardcode every remote URL forever.
A manifest-based model is often better.
Example:
{
"catalogApp": {
"version": "2.3.1",
"url": "https://cdn.company.com/catalog/2.3.1/remoteEntry.js",
"owner": "catalog-team"
},
"cartApp": {
"version": "1.8.4",
"url": "https://cdn.company.com/cart/1.8.4/remoteEntry.js",
"owner": "cart-team"
},
"checkoutApp": {
"version": "1.4.2",
"url": "https://cdn.company.com/checkout/1.4.2/remoteEntry.js",
"owner": "checkout-team"
}
}Benefits:
Version visibility
Rollback support
Environment promotion
Canary rollout
Remote ownership trackingStrong interview phrase:
The shell should know which remote to load, but not the internal implementation of that remote.
10. Shell Loading States
Remote loading should have good UX.
Possible states:
Idle
Loading remote
Remote loaded
Remote failed
Remote timed out
Fallback shown
RetryingExample:
User opens /cart
│
▼
Shell shows cart skeleton
│
▼
Cart remote loads
│
▼
Cart page rendersFor slow remotes, use skeletons.
For failed remotes, use fallback UI.
For critical remotes, consider retry and rollback.
11. Fallback UI Design
Fallback UI should match business criticality.
Marketing Remote Fails
This section is temporarily unavailable.
Catalog Remote Fails
We are having trouble loading products. Please refresh or try again.
Cart Remote Fails
Cart is temporarily unavailable. Your items are safe. Please try again.
Checkout Remote Fails
We are unable to load checkout right now. Your cart is saved. Please try again shortly.
Checkout fallback should be more careful because it affects revenue.
12. Shell Error Logging
When a remote fails, the shell should log enough context.
Log fields:
remoteName
remoteVersion
shellVersion
route
errorType
chunkUrl
manifestVersion
userJourney
teamOwner
timestampExample:
{
"remoteName": "cartApp",
"remoteVersion": "1.8.4",
"shellVersion": "3.2.0",
"route": "/cart",
"errorType": "ChunkLoadError",
"teamOwner": "cart-team"
}This makes incidents actionable.
Bad log:
Frontend error occurred.
Good log:
cartApp v1.8.4 failed to load on /cart due to ChunkLoadError.
13. Shell and Shared State
The shell should own only small platform-level state.
Appropriate shell state:
Auth identity summary
Theme
Locale
Feature flags
Global notifications
Navigation state
Experiment assignmentAvoid putting domain state in the shell:
Full cart state
Checkout form data
Product filters
Search results
Order history
Payment stateState ownership table:
| State | Owner |
|---|---|
| Theme | Shell |
| Locale | Shell |
| Auth summary | Shell/auth provider |
| Cart | Cart API + Cart Remote |
| Checkout | Checkout API/session + Checkout Remote |
| Product filters | URL + Catalog Remote |
| Search results | Search Remote |
| Orders | Orders Remote |
Strong phrase:
The shell should not become a global state dumping ground.
14. Shell and Communication
The shell can mediate simple global communication.
Example:
Cart Remote emits cart:updated
│
▼
Shell Header updates cart badgeThis is fine because the shell owns the header.
But avoid this:
Catalog Remote asks Shell to update Checkout business rules
Checkout Remote reads Catalog internal state through Shell
Cart Remote stores full cart in ShellThe shell should coordinate cross-cutting UI, not become a hidden service layer.
15. Shell and Auth Flow
Auth flow example:
User opens /orders
│
▼
Shell checks session
│
├── not logged in → redirect to login
│
└── logged in → load Orders Remote
│
▼
Orders Remote checks permissionsThe shell handles global authentication.
The remote handles feature-specific authorization.
Important:
Backend APIs must still enforce authorization. Frontend checks are not security boundaries.
16. Shell and Feature Flags
The shell can initialize flags and pass safe context to remotes.
Flag types:
Platform flags
Remote enable/disable flags
Experiment flags
Permission flags
Kill-switch flags
Canary rollout flagsExample:
checkout_new_payment_step = true
cart_drawer_v2_enabled = false
catalog_remote_rollout = 25%Shell-level use:
Should this remote load?
Which version should load?
Should fallback route be used?Remote-level use:
Should this domain feature appear?
Should this component use new behavior?17. Shell and Performance
The shell strongly affects performance.
Performance risks:
Loading all remotes upfront
Large shared shell bundle
Duplicate dependencies
Remote loading waterfall
Blocking auth before any UI appears
No preloading strategyGood practices:
Lazy load route remotes
Preload likely next remotes
Keep shell bundle small
Share React safely
Track remote load time
Avoid loading all domain code in shell
Use skeletons for remote loadingExample:
User adds item to cart
│
▼
Shell preloads Cart Remote
│
▼
User opens cart fasterFor checkout:
User opens cart
│
▼
Shell preloads Checkout Remote18. Shell Bundle Size
The shell should stay lightweight.
It should include:
Routing
Layout
Auth bootstrap
Remote loader
Platform providers
Error boundariesIt should not include:
Every remote’s components
All domain business logic
All product listing code
All checkout code
All cart codeIf the shell bundle becomes huge, micro frontends lose their performance benefit.
19. Shell Security Responsibilities
The shell should enforce safe loading rules.
Security concerns:
Only load trusted remote origins
Use HTTPS
Validate manifest source
Avoid exposing secrets
Use Content Security Policy where possible
Do not pass tokens through browser events
Protect auth contextRemember:
A remote is executable JavaScript loaded into the user’s browser.
The shell should never load arbitrary remote URLs from untrusted sources.
20. Shell Testing Strategy
Test the shell separately.
Shell tests should cover:
Route matching
Remote loading
Remote fallback
Auth guard
Feature flag bootstrap
Layout rendering
Error boundary behavior
Manifest parsing
Remote version logging
Navigation behaviorImportant integration tests:
/cart loads Cart Remote
/cart shows fallback if Cart Remote fails
/checkout requires authenticated user
/profile deep link loads Profile Remote
remote manifest missing entry shows safe errorThe shell must be extremely reliable because every route depends on it.
21. Shell Observability
The shell should capture remote runtime information.
Track:
Remote load success
Remote load failure
Remote load duration
Fallback shown
Remote version
Shell version
Route
User journey
Chunk load errors
Manifest fetch errorsExample dashboard:
Shell Dashboard
├── Remote load failure rate
├── Route-level fallback count
├── Manifest health
├── Active remote versions
├── Shell runtime errors
└── Web Vitals by routeThe shell is the best place to observe composition health.
22. Shell Ownership
The shell should usually be owned by a platform team.
Platform team owns:
Shell architecture
Remote loading standards
Route standards
Manifest format
Auth bootstrap
Feature flag bootstrap
Observability standards
Fallback patterns
Shared dependency policyDomain teams own their remotes.
Example:
| Team | Owns |
|---|---|
| Platform Team | Shell App |
| Catalog Team | Catalog Remote |
| Cart Team | Cart Remote |
| Checkout Team | Checkout Remote |
| Design System Team | UI library and tokens |
This prevents ownership confusion.
23. Shell Governance
The shell should enforce platform rules.
Governance areas:
Route naming conventions
Remote naming conventions
Manifest format
Shared dependency policy
Fallback UI requirements
Auth integration rules
Analytics event standards
Performance budgets
Security rules
Observability fieldsBut the shell should not block every domain release unnecessarily.
Strong phrase:
The shell should enable safe autonomy, not centralize all decisions.
24. Shell Anti-Patterns
| Anti-Pattern | Why It Is Bad |
|---|---|
| Shell owns all business logic | Becomes a new monolith |
| Shell stores full cart/checkout state | Tight coupling |
| Shell imports remote internals | Breaks ownership |
| Shell loads every remote upfront | Performance issue |
| Shell has no fallback UI | One remote failure breaks product |
| Shell hardcodes all versions forever | Poor deployment flexibility |
| Shell allows untrusted remote URLs | Security risk |
| Shell becomes release bottleneck | Defeats micro frontend purpose |
| Shell ignores observability | Debugging becomes hard |
| Shell owns design system implementation | Confuses platform and UI library roles |
25. Shell Design Decision Table
| Decision | Recommended Choice | Why |
|---|---|---|
| Routing | Shell owns top-level routes | Prevents route conflicts |
| Nested routes | Remote-owned | Preserves domain ownership |
| Auth | Shell bootstrap | Consistent login/session |
| Authorization | Remote + backend | Domain-specific control |
| State | Platform state only | Avoids shell monolith |
| Remote loading | Manifest-based | Supports rollback/versioning |
| Fallback | Required per remote | Failure isolation |
| Analytics | Shell initializes | Consistent instrumentation |
| Business logic | Remote/backend | Clear ownership |
| Observability | Shell captures remote health | Faster debugging |
26. Interview Questions
Q1. What is the shell app in micro frontends?
The shell app is the host/container application that provides global layout, top-level routing, authentication bootstrap, remote loading, error boundaries, and a unified user experience. It composes independently owned remotes into one product.
Q2. What should the shell own?
The shell should own platform-level concerns such as global layout, top-level routing, auth bootstrap, feature flag bootstrap, remote loading, fallback UI, global analytics initialization, and error boundaries.
Q3. What should not live in the shell?
Domain business logic should not live in the shell. Product filters, cart calculations, checkout rules, payment logic, and profile business rules should stay inside remotes or backend services.
Q4. How do you prevent the shell from becoming a monolith?
Keep the shell focused on composition concerns. Enforce clear ownership boundaries, avoid domain state in the shell, avoid importing remote internals, and keep business logic inside domain remotes or backend APIs.
Q5. How should the shell handle remote failure?
The shell should wrap remotes in error boundaries, show fallback UI, keep global navigation alive, log errors with remote name/version/route, and support retry or rollback for critical flows.
Q6. Should the shell own authentication?
The shell should usually own authentication bootstrap, session detection, token refresh, and global route protection. But remotes and backend APIs should still enforce feature-level authorization.
27. Strong Senior Answer
If an interviewer asks:
“What should the shell app do in a micro frontend architecture?”
A strong answer:
The shell app should own the platform-level composition concerns. That includes global layout, top-level routing, authentication bootstrap, remote loading, feature flag initialization, error boundaries, fallback UI, and global analytics setup. It should not own domain business logic. For example, cart calculations should belong to the Cart Remote or Cart API, product filtering should belong to the Catalog Remote, and checkout rules should belong to the Checkout Remote or backend services. I would also keep the shell lightweight. It should lazy-load remotes by route, read remote URLs from a manifest, wrap each remote in error boundaries, and log remote load failures with remote name, version, route, and shell version. The key design principle is that the shell coordinates the experience without becoming a new monolith.
28. Final Shell Checklist
- Does the shell own only platform concerns?
- Are domain business rules kept out of the shell?
- Does the shell own top-level routing?
- Do remotes own nested/domain routing?
- Does the shell bootstrap auth?
- Do remotes handle feature-level authorization?
- Does the shell load remotes through a manifest?
- Does each remote have fallback UI?
- Are remote load errors logged with version and owner?
- Is the shell bundle kept small?
- Are critical remotes preloaded appropriately?
- Is untrusted remote loading blocked?
- Are feature flags initialized safely?
- Is the shell observable?
- Is ownership clearly defined?
29. Summary
The shell app is the backbone of a micro frontend architecture.
It should provide:
Unified layout
Top-level routing
Authentication bootstrap
Remote loading
Fallback UI
Error isolation
Feature flag bootstrap
Global analytics
ObservabilityBut it should not become a dumping ground for business logic.
The best shell apps are boring, stable, lightweight, and platform-focused.
Final takeaway:
The shell should compose the product experience, not become the product’s business brain.
References
- Micro Frontends — Martin Fowler (https://martinfowler.com/articles/micro-frontends.html)
- Micro Frontends (https://micro-frontends.org)
- webpack Module Federation Documentation (https://webpack.js.org/concepts/module-federation/)
- AWS Prescriptive Guidance: Micro-frontends (https://docs.aws.amazon.com/prescriptive-guidance/latest/micro-frontends-aws/introduction.html)
- Module Federation Official Site (https://module-federation.io)