Article Guide
Senior14 min readJune 13, 2026

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.

Tags:
Micro FrontendsShell AppFrontend ArchitectureModule FederationSystem DesignInterview Prep

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:

textEditor
Host app
Container app
Platform app
Composition app
Root app

Its job is to compose multiple independently owned frontend applications into one user experience.

Example:

textEditor
Shell App
├── Header
├── Navigation
├── Auth Bootstrap
├── Route Config
├── Remote Loader
├── Error Boundaries
└── Loads:
    ├── Catalog Remote
    ├── Cart Remote
    ├── Checkout Remote
    ├── Profile Remote
    └── Orders Remote

The 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:

textEditor
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 journey

The 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

architecture diagram
                    ┌──────────────────────┐
                    │       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:

textEditor
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 awareness

These 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:

textEditor
Header
Footer
Sidebar
Global navigation
Page frame
Global notification area

Example:

textEditor
Shell Layout
├── Header
├── Main Content Slot
├── Footer
└── Toast Region

The remote renders inside the content slot.

textEditor
Shell App
└── Main Content Slot
    └── Catalog Remote

This keeps the user experience consistent.

5.2 Top-Level Routing

The shell should usually own top-level route decisions.

Example:

textEditor
/                         → 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 Remote

The shell decides which remote should load for each top-level route.

The remote can own nested routes inside its domain.

textEditor
Profile Remote owns:
/profile/details
/profile/addresses
/profile/preferences

Strong 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:

textEditor
Login detection
Session bootstrap
Token refresh
Logout handling
Auth provider setup
Global auth guard
Identity context

Example:

textEditor
Shell starts
  │
  ▼
Check session
  │
  ▼
Load identity summary
  │
  ▼
Render protected route
  │
  ▼
Pass safe identity context to remote

But 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:

textEditor
User opens /cart
      │
      ▼
Shell matches /cart route
      │
      ▼
Shell reads remote manifest
      │
      ▼
Shell loads cart remoteEntry.js
      │
      ▼
Cart Remote renders

The shell should handle:

textEditor
Remote URL resolution
Remote version lookup
Loading state
Timeout
Error handling
Fallback UI
Retry if appropriate

This is one of the shell’s most important production responsibilities.

5.5 Error Boundaries

Each remote should be wrapped in an error boundary.

textEditor
Shell App
├── Catalog Remote
│   └── Error Boundary
├── Cart Remote
│   └── Error Boundary
└── Checkout Remote
    └── Error Boundary

If a remote fails, the shell should not go blank.

Expected behavior:

textEditor
Cart Remote fails
      │
      ▼
Shell catches error
      │
      ▼
Fallback UI shown
      │
      ▼
Error logged with remote name/version
      │
      ▼
Header and navigation remain usable

5.6 Feature Flag Bootstrap

The shell can initialize the feature flag client.

Example:

textEditor
Shell loads user context
      │
      ▼
Shell initializes feature flags
      │
      ▼
Remote receives relevant flag context

Feature flags may control:

textEditor
Which remote version is loaded
Whether a new remote is enabled
Whether a new checkout step is shown
Canary rollout percentage
Kill switch behavior

But domain-specific feature behavior should still live inside the owning remote.

5.7 Global Analytics Initialization

The shell can initialize analytics.

It can own:

textEditor
Analytics SDK setup
Page view tracking
Route transition tracking
Global user/session fields
Remote version metadata

Remotes can emit domain events:

textEditor
catalog:product-clicked
cart:item-added
checkout:completed
profile:address-updated

The 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:

textEditor
Theme
Color mode
Typography baseline
Locale
Direction
Global CSS reset
Design tokens

But 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:

textEditor
Product filtering logic
Cart calculation logic
Checkout validation rules
Payment flow logic
Order history business rules
Profile form business logic
Search ranking logic
Promotion rules

Bad shell:

textEditor
Shell App
├── Header
├── Routing
├── Auth
├── Cart calculation
├── Checkout rules
├── Product filters
├── Search business logic
└── Order history logic

This is just a new monolith.

Good shell:

textEditor
Shell App
├── Layout
├── Routing
├── Auth bootstrap
├── Remote loading
├── Error boundaries
├── Feature flags
└── Analytics bootstrap

Domain logic remains inside remotes or backend services.

7. Shell vs Remote Responsibility Table

ConcernShell OwnsRemote Owns
Global layoutYesNo
Top-level routingYesNo
Nested domain routingNoYes
Auth bootstrapYesNo
Feature-level authorizationNoYes
Product filtersNoCatalog Remote
Cart business logicNoCart Remote
Checkout stepsNoCheckout Remote
Remote loadingYesNo
Error boundary wrapperYesRemote can add local boundaries too
Domain API callsNoYes
Global analytics setupYesDomain events
Design system usageProvides contextUses components

8. Shell Routing Design

A typical route map:

textEditor
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:

textEditor
/profile/addresses
      │
      ▼
Shell matches /profile/*
      │
      ▼
Shell loads Profile Remote
      │
      ▼
Profile Remote renders Addresses page

This 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:

jsonEditor
{
  "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:

textEditor
Version visibility
Rollback support
Environment promotion
Canary rollout
Remote ownership tracking

Strong 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:

textEditor
Idle
Loading remote
Remote loaded
Remote failed
Remote timed out
Fallback shown
Retrying

Example:

textEditor
User opens /cart
      │
      ▼
Shell shows cart skeleton
      │
      ▼
Cart remote loads
      │
      ▼
Cart page renders

For 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:

textEditor
remoteName
remoteVersion
shellVersion
route
errorType
chunkUrl
manifestVersion
userJourney
teamOwner
timestamp

Example:

jsonEditor
{
  "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:

textEditor
Auth identity summary
Theme
Locale
Feature flags
Global notifications
Navigation state
Experiment assignment

Avoid putting domain state in the shell:

textEditor
Full cart state
Checkout form data
Product filters
Search results
Order history
Payment state

State ownership table:

StateOwner
ThemeShell
LocaleShell
Auth summaryShell/auth provider
CartCart API + Cart Remote
CheckoutCheckout API/session + Checkout Remote
Product filtersURL + Catalog Remote
Search resultsSearch Remote
OrdersOrders 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:

textEditor
Cart Remote emits cart:updated
      │
      ▼
Shell Header updates cart badge

This is fine because the shell owns the header.

But avoid this:

textEditor
Catalog Remote asks Shell to update Checkout business rules
Checkout Remote reads Catalog internal state through Shell
Cart Remote stores full cart in Shell

The shell should coordinate cross-cutting UI, not become a hidden service layer.

15. Shell and Auth Flow

Auth flow example:

textEditor
User opens /orders
      │
      ▼
Shell checks session
      │
      ├── not logged in → redirect to login
      │
      └── logged in → load Orders Remote
                           │
                           ▼
                    Orders Remote checks permissions

The 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:

textEditor
Platform flags
Remote enable/disable flags
Experiment flags
Permission flags
Kill-switch flags
Canary rollout flags

Example:

textEditor
checkout_new_payment_step = true
cart_drawer_v2_enabled = false
catalog_remote_rollout = 25%

Shell-level use:

textEditor
Should this remote load?
Which version should load?
Should fallback route be used?

Remote-level use:

textEditor
Should this domain feature appear?
Should this component use new behavior?

17. Shell and Performance

The shell strongly affects performance.

Performance risks:

textEditor
Loading all remotes upfront
Large shared shell bundle
Duplicate dependencies
Remote loading waterfall
Blocking auth before any UI appears
No preloading strategy

Good practices:

textEditor
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 loading

Example:

textEditor
User adds item to cart
      │
      ▼
Shell preloads Cart Remote
      │
      ▼
User opens cart faster

For checkout:

textEditor
User opens cart
      │
      ▼
Shell preloads Checkout Remote

18. Shell Bundle Size

The shell should stay lightweight.

It should include:

textEditor
Routing
Layout
Auth bootstrap
Remote loader
Platform providers
Error boundaries

It should not include:

textEditor
Every remote’s components
All domain business logic
All product listing code
All checkout code
All cart code

If the shell bundle becomes huge, micro frontends lose their performance benefit.

19. Shell Security Responsibilities

The shell should enforce safe loading rules.

Security concerns:

textEditor
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 context

Remember:

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:

textEditor
Route matching
Remote loading
Remote fallback
Auth guard
Feature flag bootstrap
Layout rendering
Error boundary behavior
Manifest parsing
Remote version logging
Navigation behavior

Important integration tests:

textEditor
/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 error

The shell must be extremely reliable because every route depends on it.

21. Shell Observability

The shell should capture remote runtime information.

Track:

textEditor
Remote load success
Remote load failure
Remote load duration
Fallback shown
Remote version
Shell version
Route
User journey
Chunk load errors
Manifest fetch errors

Example dashboard:

textEditor
Shell Dashboard
├── Remote load failure rate
├── Route-level fallback count
├── Manifest health
├── Active remote versions
├── Shell runtime errors
└── Web Vitals by route

The 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:

textEditor
Shell architecture
Remote loading standards
Route standards
Manifest format
Auth bootstrap
Feature flag bootstrap
Observability standards
Fallback patterns
Shared dependency policy

Domain teams own their remotes.

Example:

TeamOwns
Platform TeamShell App
Catalog TeamCatalog Remote
Cart TeamCart Remote
Checkout TeamCheckout Remote
Design System TeamUI library and tokens

This prevents ownership confusion.

23. Shell Governance

The shell should enforce platform rules.

Governance areas:

textEditor
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 fields

But 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-PatternWhy It Is Bad
Shell owns all business logicBecomes a new monolith
Shell stores full cart/checkout stateTight coupling
Shell imports remote internalsBreaks ownership
Shell loads every remote upfrontPerformance issue
Shell has no fallback UIOne remote failure breaks product
Shell hardcodes all versions foreverPoor deployment flexibility
Shell allows untrusted remote URLsSecurity risk
Shell becomes release bottleneckDefeats micro frontend purpose
Shell ignores observabilityDebugging becomes hard
Shell owns design system implementationConfuses platform and UI library roles

25. Shell Design Decision Table

DecisionRecommended ChoiceWhy
RoutingShell owns top-level routesPrevents route conflicts
Nested routesRemote-ownedPreserves domain ownership
AuthShell bootstrapConsistent login/session
AuthorizationRemote + backendDomain-specific control
StatePlatform state onlyAvoids shell monolith
Remote loadingManifest-basedSupports rollback/versioning
FallbackRequired per remoteFailure isolation
AnalyticsShell initializesConsistent instrumentation
Business logicRemote/backendClear ownership
ObservabilityShell captures remote healthFaster 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:

textEditor
Unified layout
Top-level routing
Authentication bootstrap
Remote loading
Fallback UI
Error isolation
Feature flag bootstrap
Global analytics
Observability

But 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)