skip to content
[··]nano-os v7.0 — booting portfolio
revival nano adolor
senior frontend engineer
00%
← notes

Turning a React Native app into a mini-app shell

2026-08-102 min read#react-native#architecture#redux

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.
✓