How would you architect a React Native app for cross-platform reuse?
React Native Developer
answer
A scalable React Native architecture uses a monorepo with shared TypeScript packages for domain logic, UI primitives, and utilities, plus platform adapters for sensors, storage, and navigation. React Native Web renders the same components on web. File suffixes (.ios.tsx, .android.tsx, .web.tsx) and dependency inversion expose platform features behind interfaces. Use a design system, theming, and Storybook for reuse, while native modules (TurboModules/JSI) unlock device-specific power when needed.
Long Answer
Designing a cross-platform React Native architecture that maximizes code reuse while supporting platform-specific features requires clear seams: shared domain logic, reusable UI primitives, and thin platform adapters. The goal is to write business code once, compose UI consistently, and branch only where devices or form factors genuinely differ.
1) Monorepo and package boundaries
Start with a monorepo (Turborepo or Nx) that hosts apps/ for iOS, Android, and Web, plus packages/ for ui (design system), features (screen bundles), core (domain logic), and platform (adapters). All shared code is TypeScript. The app shells are minimal entry points that wire navigation, configuration, and platform providers. This shape enables parallel work and fast incremental builds.
2) Design system and primitives
Build a design system in packages/ui with cross-platform primitives (Text, Button, Stack, Icon) that abstract platform quirks. Use React Native Web to render primitives on web with the same props. Theme through tokens (spacing, color, typography) and CSS-in-JS that supports native and web (for example, react-native-unistyles or tamagui). Document everything with Storybook so teams reuse rather than fork.
3) Feature modules and routing
Group screens into packages/features/<domain> with containers, hooks, and tests. Navigation lives in the app shell using React Navigation for native and the web router for web, but screen components come from the same feature module. Deep links map to the same route names across platforms to preserve parity. Feature flags control rollout without branching code.
4) Dependency inversion for platform services
Expose sensors, secure storage, push notifications, file system, and media through interfaces in packages/platform. Implement adapters per platform using .ios.ts, .android.ts, .web.ts. At runtime, Metro and Webpack pick the correct file automatically. This keeps domain and UI code unaware of platform details while allowing platform-specific performance tweaks.
5) State, data, and offline strategy
Use a predictable state layer (Redux Toolkit or Zustand) for client state and a data client (React Query or Apollo) for server state. Normalize entities once and share selectors across platforms. Add an offline policy with a persistent store (MMKV, SQLite, or WatermelonDB) and background sync through queues. Keep business rules in packages/core as pure TypeScript so tests run quickly in Node.
6) Performance and rendering
Enable Hermes on native, use the Fabric renderer, and prefer TurboModules/JSI for heavy native bridges. For animations, use Reanimated to avoid expensive JS-to-native hops. Memoize list rows, window large lists, and defer noncritical work with transitions. On the web, use React Native Web with tree-shaken design tokens, image optimization, and route-level code splitting to keep Time to Interactive low.
7) Platform-specific UI and accessibility
Use .ios.tsx, .android.tsx, and .web.tsx only when the platform truly differs (for example, date pickers, share sheets, context menus). Wrap these differences inside components so screens remain identical. Follow accessibility traits and roles per platform, and verify with automated checks. Respect platform conventions for navigation gestures and system theming.
8) Testing and quality gates
Adopt a pyramid: unit tests for core business logic, component tests for ui, and integration tests for feature flows with React Native Testing Library. Add Detox for native end-to-end and Playwright or Cypress for web. Storybook doubles as a visual spec. TypeScript strict mode, ESLint, and Prettier enforce consistency; danger bots flag bundle growth and untyped boundaries.
9) Delivery, updates, and observability
Use Fastlane or EAS for native builds and OTA updates (Expo Updates or CodePush) for safe JavaScript delivery. Set up Sentry or similar for crash and error tracking, and a performance monitor for app start, interaction latency, and memory. Log analytics with shared events so dashboards compare iOS, Android, and web on equal footing.
10) When to introduce native modules
Start with community modules. Introduce custom TurboModules or JSI bindings only for capabilities that demand them (advanced media, cryptography, low-latency sensors). Keep the interface in packages/platform so higher layers remain unchanged. Document build steps and add CI checks for ABI drift.
This approach yields a React Native architecture that maximizes shared code, keeps platform differences contained behind adapters and file suffixes, and delivers consistent UX with native performance where it matters. Teams move fast because most work happens once, while the design and platform layers keep the application feeling at home on iOS, Android, and the web.
Table
Common Mistakes
- Duplicating screens per platform instead of abstracting differences behind components.
- Mixing platform APIs directly into features, making reuse impossible.
- Skipping a design system, leading to one-off styles that do not port to web.
- Overusing context and global state, causing unnecessary re-renders and sluggish lists.
- Ignoring offline behavior and retries, which breaks mobile reliability.
- Relying on heavy web libraries that do not tree-shake in React Native Web.
- Implementing custom native modules before exhausting community and adapter options.
- No Storybook, no visual specs, and limited tests, so regressions multiply during fast releases.
Sample Answers (Junior / Mid / Senior)
Junior:
“I would place iOS, Android, and web in one monorepo with shared TypeScript packages. I would build a small design system so screens reuse the same components. I would use React Native Web on the web and file suffixes for platform differences. State would use Redux Toolkit, and I would add tests with React Native Testing Library.”
Mid:
“My React Native architecture has core for business logic, ui for primitives, and platform for adapters like storage and notifications. Features import these layers, so screens stay identical across platforms. React Navigation handles routes, and React Query manages server data with offline cache. Hermes, Fabric, and Reanimated keep interactions smooth. CI builds native apps and deploys OTA updates.”
Senior:
“I structure a monorepo with strict package boundaries and dependency inversion. Platform capabilities live behind interfaces with .ios/.android/.web implementations. A token-based design system plus Storybook maximizes reuse. I enforce performance budgets, windowed lists, and code splitting on web. Custom TurboModules appear only when proven necessary. Delivery uses EAS or Fastlane, OTA updates, and unified observability across platforms.”
Evaluation Criteria
Strong responses present a React Native architecture that maximizes reuse through a monorepo, shared TypeScript packages, and a design system, while isolating platform features behind adapter interfaces and file suffixes. They describe navigation parity, data layers that work offline, and performance practices (Hermes, Fabric, Reanimated, windowed lists). They include testing across layers, CI/CD with OTA updates, and unified analytics and error tracking. Red flags include duplicating screens per platform, calling platform APIs directly in features, skipping a design system, ignoring offline concerns, and introducing custom native modules prematurely.
Preparation Tips
- Create a monorepo with apps/{ios,android,web} and packages/{ui,core,platform,features}.
- Build three UI primitives and theme tokens; render them in Storybook and on web via React Native Web.
- Implement a Storage interface with .ios/.android/.web adapters; write a feature that persists user settings once.
- Add React Navigation routes that map to the same feature screens across platforms and support deep links.
- Wire React Query with an offline cache and a retry policy; simulate airplane mode.
- Measure list performance, add windowing, and verify frame rate.
- Configure CI to run tests, lint, type checks, and produce native builds; add OTA deployment.
- Add Sentry and analytics with a shared event schema to compare iOS, Android, and web.
Real-world Context
A marketplace adopted a monorepo with ui, core, and platform packages and React Native Web for desktop. Shared primitives delivered identical screens with minimal overrides, cutting duplicate code by more than a third. Moving sensors, storage, and notifications behind interfaces allowed the team to add a web push adapter without touching features. Enabling Hermes and Reanimated stabilized frame rate on large lists, while windowing removed jank. OTA updates fixed a billing bug across platforms in hours, and unified analytics revealed an Android-specific crash path that web did not show. The result was faster delivery, consistent UX, and targeted platform work only where it mattered.
Key Takeaways
- Use a monorepo and shared TypeScript packages to maximize reuse.
- Hide platform specifics behind adapter interfaces and file suffixes.
- Build a design system and use React Native Web for web parity.
- Prioritize performance with Hermes, Fabric, Reanimated, and windowed lists.
- Ship confidently with CI/CD, OTA updates, tests, and unified observability.
Practice Exercise
Scenario:
You are building a cross-platform React Native client for a notes product with rich lists, offline access, push notifications, and a web dashboard. Leadership wants maximum code reuse, native-feeling performance, and platform-specific polish where it counts.
Tasks:
- Propose a monorepo layout: apps/{ios,android,web} and packages ui, features/notes, core, and platform. Describe ownership and dependency rules between packages.
- Define a Storage interface in platform with methods get, set, remove, and subscribe. Implement .ios.ts with Keychain plus file storage, .android.ts with EncryptedSharedPreferences plus file storage, and .web.ts with IndexedDB.
- Build a tokenized ListItem and List in ui; add Storybook stories that render identically on native and web.
- Create a notes feature screen that reads from React Query, caches offline via MMKV or SQLite on native and IndexedDB on web, and window large lists.
- Add push notification handling via an adapter that routes to the same openNote command on all platforms.
- Use React Navigation with a deep link that opens a specific note on all platforms; ensure route names match.
- Add tests: unit tests for core note logic, component tests for ListItem, and an end-to-end flow (Detox for native, Playwright for web).
- Configure CI to run type checks, tests, lint, build native apps, build web, and publish an OTA update. Add Sentry and a shared analytics event note_opened with platform tags.
Deliverable:
An architecture note, a skeleton repo plan, and a minimal proof of concept demonstrating React Native architecture that maximizes code reuse across iOS, Android, and web while supporting platform-specific features cleanly.

