MetaMask / MetaMask/metamask-mobile
Code-split the AppFlow navigator's 150+ eager screen imports off the boot path
- Dominant language
- TypeScript
- Stars
- 3k
- Forks
- 1.7k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 669
Description
> **Performance audit finding** · Severity: **High** · Effort: Hard · Fix risk: Risky · Test safety net: Uncovered
> Owner: `@MetaMask/mobile-platform (suggested)`
> File: `app/components/Nav/App/App.tsx:7`
### What is this about?
`app/components/Nav/App/App.tsx` statically imports **152 modules** at the top of the file — essentially every screen, sheet, and modal the app can ever show (Login, Onboarding, all MultichainAccounts sheets, Ledger/QR hardware flows, Ramp bootstrap, Perps/Predict toast hooks, confirmation modals, etc.). `App` is rendered by `Root` (`app/components/Views/Root/index.tsx:5`), which is the component registered in `AppRegistry.registerComponent` (`index.js:124`). Because every `import` is a top-level static import, Metro/Hermes must evaluate every one of those module factories (and their transitive dependency graphs) during the **initial JS bundle evaluation**, before the first frame can be painted. No `React.lazy`/dynamic `import()` is used anywhere in the navigation tree (a repo-wide search for `React.lazy`/`lazy(` in `app/` returns zero hits).
**Why it matters**
Module-factory execution for ~150 screens (plus their deep imports) runs synchronously on the JS thread at startup and directly inflates time-to-interactive. The vast majority of these screens are never shown during a cold start (the user lands on Login or the wallet home); paying their evaluation cost up front is wasted boot work. This is the single largest deferrable chunk on the JS startup path that does not require touching the Engine.
### Scenario
N/A — see Technical Details.
### Design
N/A — internal performance change; no UI/design impact.
### Technical Details
**Evidence**
`app/components/Nav/App/App.tsx:1-168` — 152 top-level `import` statements feeding one `createStackNavigator()`:
```ts
const Stack = createStackNavigator();
// ...AppFlow registers ~70 entries, each referencing an eagerly-imported component
```
`index.js:124` registers `Root`, which renders `` (`app/components/Views/Root/index.tsx:92`), so all of `App.tsx`'s imports are on the cold-start evaluation path.
**Fix**
Convert rarely-used screen components to lazily-loaded modules so their factories are not evaluated at boot: wrap them with `React.lazy(() => import('...'))` and render inside a `` boundary, or extract the modal/sheet-only `Stack.Screen` groups (e.g. `RootModalFlow`, hardware-wallet flows, MultichainAccounts sheets) into separate files imported via dynamic `import()`. Keep only the cold-start-critical screens (`FoxLoader`, `Login`, `Main`) statically imported. Measure with the existing `TraceName.UIStartup` / `NavInit` spans before/after.
### Threat Modeling Framework
N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.
### Acceptance Criteria
- 1. Add a `console.time`/Sentry span around bundle evaluation, or use the existing `getUIStartupSpan()` traces.
2. Profile cold start with the Hermes sampling profiler; confirm reduced module-evaluation time for the navigation chunk.
3. Smoke-test every lazily-loaded route still mounts (no `Suspense` fallback flashes on first navigation) across onboarding, hardware wallet, and modal flows.
### References
- File: `app/components/Nav/App/App.tsx:7`
- Source: MetaMask Mobile performance audit — finding `startup-appflow-eager-screen-imports`
- Owner (CODEOWNERS / best-effort): @MetaMask/mobile-platform (suggested)
- Status: **UNVALIDATED**
Contributor guide
Research direction
Start with app/components/Nav/App/App.tsx, then trace Root in app/components/Views/Root/index.tsx and registration in index.js:124. Review the existing TraceName.UIStartup, NavInit, and getUIStartupSpan() instrumentation, then profile cold start with Hermes. Done means reduced navigation module-evaluation time while onboarding, hardware-wallet, and modal routes still mount without fallback flashes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- mobile, performance
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 32/100