Turning a React Native app into a mini-app shell
Super-apps look like one app and behave like several. Kwikpik's customer app got there the usual way — a delivery app that grew a wallet, then bills, then transfers — until every release touched the same navigation tree and the same store. This is how we pulled it apart without a rewrite.
1. Each mini-app registers itself
A mini-app is a folder with an index.ts that exports a config:
const finance: MiniAppConfig = {
id: "finance",
navigation: { tab: { label: "Wallet", icon: WalletIcon } },
screens: financeScreens,
};
registerReducer("finance", financeReducer);
export default finance;The shell keeps a list of lazy imports and an AppRegistry that loads them on boot. Adding a product means adding a folder and one line — not editing core navigation.
The registry also rejects impossible configs at registration: a mini-app can be a tab or own a root navigator, not both. That's an error on boot in development, not a broken screen in QA.
2. Reducers arrive when the mini-app does
Core slices — auth, user, UI — are always in the store. Mini-app slices are added at runtime through a small reducer manager, after redux-persist rehydrates, so persisted mini-app state doesn't get overwritten by an initial state that loaded first.
export function registerReducer(key: string, reducer: Reducer) {
reducerManager.add(key, reducer);
store.replaceReducer(reducerManager.reduce);
}3. Cross-app talk goes through an event bus
Mini-apps don't import each other. When a transfer completes, finance emits wallet:updated; dispatch, which shows a balance on its checkout, listens. Small, explicit, greppable.
4. One design system for both apps
The kwikpik-ui package — tokens, typography, components — lives in a pnpm workspace shared with the rider app. A design change is one PR, reviewed once.
What I'd tell my past self
- Draw the boundary at state, not at screens. Screens move easily; shared state is what makes things a monolith.
- Make illegal configs throw early. A registry is a great place for assertions.
- Keep the event bus boring. If you need request/response between mini-apps, you've found a shared service, not an event.