What is the App Shell pattern?
The shell is the part of the app that is always there: the frame, navigation, sign-in, error handling, analytics and access to the design system. On the web it loads micro-frontends by route. On mobile it is the core module of a modular app, and each team ships a feature module that registers its screens with it. The shell holds no business features of its own.
What problem does the App Shell pattern solve?
When features are built by several teams, someone has to own what they share. Without a shell, each team solves sign-in, navigation and telemetry in its own way, and the app feels like several apps stitched together. A shell solves those concerns once, so feature teams only build features.
How do you implement the App Shell pattern?
- Keep the shell small: layout, routing, session, error boundaries, telemetry and a few platform services.
- Define a narrow contract for features to plug in: how they register routes or screens, what context they receive, how they report errors.
- Pass the user session and trace context to every feature through that contract, not through globals.
- On mobile, put shared code in a core module and give each team its own feature module with a clear interface.
- Version the contract carefully and release the shell on its own schedule, so features never need to change with it.
When should you use it?
- Several teams contribute features to one web or mobile app.
- Sign-in, navigation and telemetry are being solved differently in different parts of the app.
When should you avoid it?
- One team owns the whole app. Normal modules inside one codebase are enough.
What do you gain?
- Cross-cutting concerns are solved once
- Feature teams start fast and stay consistent
- One place to enforce performance and accessibility standards
What does it cost?
- The shell team can become a bottleneck
- A change to the contract affects every feature
- Shell bugs affect the whole app
Who usually owns the App Shell pattern?
Platform team. A platform team builds and runs this once, as a self-service product that every product team uses without rebuilding it. See how teams map to layers and patterns.
What are common mistakes with the App Shell pattern?
- Business features creeping into the shell because it is the easiest place to put them.
- A shell that must be released for every feature change, which turns it back into a monolith.
- Features depending on undocumented shell internals that break on the next shell release.
App Shell in practice
Uber
Uber's mobile apps are built by a large number of engineers working on the same codebase, on both iOS and Android.
Uber built RIBs, the cross-platform architecture framework behind many of its mobile apps, designed for apps with many engineers and nested states. In RIBs, business logic drives the app's structure rather than the view tree, and iOS and Android share the same architecture so their teams can review each other's code. Uber open-sourced it with tooling for code generation and static analysis, and says it has scaled to hundreds of engineers on one codebase.
Source: RIBs README (Uber, GitHub)
Example decision record
- Context
- Several teams build features in one app and each solves sign-in, navigation and telemetry differently.
- Decision
- Create an app shell, owned by a platform team, that provides the session, navigation, telemetry and error handling, with a narrow contract for features to plug in.
- Consequences
- Cross-cutting behaviour is consistent across the app.
- The shell must stay free of business features, or it becomes a bottleneck.
Test yourself on App Shell
After moving to micro-frontends, the product page loads three different versions of the same UI framework and has doubled in size. What is the best response?
3 questions involve this pattern. Practice all of them →
Saved in this browser only. See your progress →