Opinionated rules for building high-performance, cross-platform mobile apps with Expo, React Native New Architecture, and TypeScript.
Transform your React Native development from a series of context switches into a smooth, predictable workflow. These Cursor Rules eliminate the friction between TypeScript strictness, cross-platform consistency, and performance optimization—letting you ship faster without compromising quality.
You're building for iOS, Android, and web simultaneously. You need type safety without boilerplate hell. You want React Native's New Architecture benefits without breaking existing code. You're managing state across complex navigation flows while maintaining 60fps animations.
The reality: Most React Native setups leave you juggling:
These rules establish a battle-tested React Native + Expo + TypeScript development environment that enforces the New Architecture while maintaining code quality. Instead of piecing together configurations, you get a complete system that works from day one.
What you get immediately:
--noImplicitOverride, --exactOptionalPropertyTypes) catch edge cases during developmentsatisfies keyword usage maintains literal inference while validating shapes// Before: Loose typing leads to runtime errors
const user = await fetchUser(id); // any type, potential null errors
// After: Strict patterns with early returns
interface User { id: string; name: string; verified: boolean }
const result = await fetchUser(id);
if (!result.success) return handleError(result.error);
const user = result.data; // fully typed, errors impossible
Camera.isAvailableAsync())estimatedItemSize configurationimport { Button } from 'tamagui/button'Before: Passing data between screens breaks with complex navigation structures
// Fragile: Breaks when navigation structure changes
navigation.navigate('Profile', { userId: user.id });
After: Strongly-typed navigation with guaranteed type safety
type RootStackParamList = {
Profile: { userId: string; mode: 'view' | 'edit' };
Settings: undefined;
};
// Type-safe navigation with IDE autocomplete
navigation.navigate('Profile', {
userId: user.id,
mode: 'edit' // TypeScript enforces valid values
});
Before: API errors crash the app or show generic error messages
// Fragile: Network errors crash components
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(id).then(setUser); // crashes on network error
}, [id]);
After: Bulletproof error handling with user-friendly fallbacks
const fetchProfile = async (id: string) => {
if (!id) return Err('MISSING_ID');
try {
const res = await api.get(`/users/${id}`);
return Ok(res.data);
} catch (e) {
return Err(parseError(e));
}
};
// Component handles all error states gracefully
const { data: user, error } = useQuery(['profile', id], () => fetchProfile(id));
if (error) return <ErrorFallback error={error} />;
Before: Janky animations that drop frames during navigation
// 30fps animations, blocks JavaScript thread
<Animated.View style={{ opacity: fadeAnim }}>
After: Smooth 60fps animations that run on the UI thread
// Reanimated v3 Worklets run on UI thread
const opacity = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
opacity: withSpring(opacity.value)
}));
# Create new Expo project with TypeScript
npx create-expo-app --template tabs MyApp
cd MyApp
# Enable New Architecture
export EXPO_USE_STATIC_FRAMEWORKS=1
npx expo prebuild --clean
.cursorrules in your project rootnpx expo install @shopify/flash-list react-native-reanimated expo-image react-native-mmkv
Update tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true
}
}
app/ # Expo Router entry points
components/ # Reusable UI primitives
features/ # Domain-specific screens + logic
├── auth/
├── profile/
└── settings/
store/ # Global state management
utils/ # Pure helper functions
# Should compile without errors
npx expo prebuild --clean --non-interactive
The rules transform React Native development from a configuration nightmare into a predictable, high-performance development experience. Your apps run better, your code is more maintainable, and your team ships features faster.
You are an expert in React Native (New Architecture), Expo, TypeScript 5+, Redux Toolkit, Recoil/Zustand, React Navigation v6, React-Native-Reanimated v3, NativeWind, Gluestack UI, Tamagui, FlashList, Lottie, Jest + React-Native-Testing-Library, EAS Build, and mobile CI/CD.
Key Principles
- Ship one shared codebase for iOS, Android & Web while respecting platform idioms.
- Write **strict, explicit, immutable TypeScript**; no `any`, no implicit `any`, no `!` non-null assertions.
- Prefer **functional, declarative programming**; never use `class` components.
- Keep functions <40 LOC; extract pure helpers into `/utils`.
- Favour **composition over inheritance**; nest providers only once per concern.
- colocation rule: keep files close to where they are consumed; avoid `/components/common` buckets.
- Treat performance as a feature: measure first (Flipper, Hermes profiler), optimise later.
- Fail fast & loudly; return early on invalid state, then handle happy path.
- All code must compile with `expo prebuild --clean`, **new architecture (Fabric + TurboModules) enabled**.
TypeScript
- Use the `--strict`, `--noImplicitOverride`, `--exactOptionalPropertyTypes` compiler flags.
- File extensions: `.tsx` for components/hooks, `.ts` for logic; no mixed default export per file.
- Types first → infer where possible: `const foo = bar()`; write type guards when narrowing.
- Prefer `interface` for objects meant to be implemented, `type` for unions & compositions.
- Use `satisfies` to keep literal inference while validating shape.
- Example
```ts
interface Todo { id: string; text: string; done: boolean }
const todos = [{ id: '1', text: 'Install app', done: false }] satisfies Todo[];
```
Error Handling & Validation
- Wrap every async boundary (`api`, `FileSystem`, `Linking`) with `try/catch` + custom `AppError` union.
- Use `zod` or `io-ts` to validate external data; never trust fetch body.
- Surface user-facing errors via a global `ErrorBoundary` + Toast; log technical stack to Sentry.
- Check platform capabilities before use (e.g., `Camera.isAvailableAsync`).
- Early return pattern:
```ts
export async function fetchProfile(id: string) {
if (!id) return Err('MISSING_ID');
try { const res = await api.get(`/users/${id}`); return Ok(res.data); }
catch (e) { return Err(parseError(e)); }
}
```
React Native + Expo (Framework Rules)
- New Architecture
• Enable with `expo prebuild` + `EXPO_USE_STATIC_FRAMEWORKS=1`;
• Prefer libraries that support Fabric/TurboModules (`@shopify/flash-list`, `react-native-reanimated@>=3`).
- Project structure
• `app/` – entry route groups (Expo Router)
• `components/` – UI primitives
• `features/<domain>/` – screens + slice + hooks + types
• `store/` – Redux Toolkit, Zustand or Recoil atoms/selectors
• `utils/`, `services/`, `hooks/` – shared logic.
- Navigation
• Use Expo Router or React Navigation v6 with strongly-typed params: `type RootStackParamList`.
• Always wrap navigators in `<NavigationContainer linking={linking} theme={theme}>`.
- Styling
• Choose one: NativeWind, Tamagui or Gluestack UI. Do NOT mix utility systems.
• Keep <10 custom colours; use design-token files exported from Figma → `tailwind.config.js`.
- State Management
• For global, async data → Redux Toolkit + RTK Query.
• For local UI state → useContext + useReducer or Zustand.
• Never dispatch inside React render; wrap actions in `useCallback`.
- Animations
• Use Reanimated v3 with Worklets; no `LayoutAnimation` for complex flows.
• Lottie files must be trimmed & compressed (<50kB) before bundling.
- Lists
• Default to `FlashList`; set `estimatedItemSize` & `onLoadMore` thresholds.
- Native Modules
• If unavoidable, expose via TurboModule with identical TS types.
Testing
- Unit tests: Jest 29 + `@testing-library/react-native` with `jest-expo` preset.
- E2E: Detox for native, Playwright for web.
- Require 80% line coverage on all PRs via `coveralls`.
- Snapshot test only static, deterministic components.
Performance
- Enable Hermes everywhere (`expo prebuild` sets automatically).
- Use `expo-image` for caching, `react-native-mmkv` for storage.
- Guard heavy components with `React.lazy` + `Suspense` on web; on native use code-splitting via EAS build profiles.
- Profile start-up; aim <1.5 s cold start on mid-range Android.
- Adopt selective imports in Gluestack/Tamagui: `import { Button } from 'tamagui/button'`.
Security & Permissions
- Isolate secrets with `expo-config-plugins` + `ENV` files excluded from repo.
- Request permissions at runtime, just-in-time; fallback UI for denied state.
- Enforce TLS pinning with `expo-ssl-pinning` for critical APIs.
Accessibility
- Every touchable: `accessibilityRole`, `accessibilityLabel`.
- Maintain min 44×44 dp interactive targets.
- Use `react-native-testing-library` `toBeAccessible()` for automated a11y checks.
CI/CD & Deployment
- Use EAS Build with separate profiles: `development`, `preview`, `production`.
- Run `expo doctor` and `expo prebuild --clean --non-interactive` in CI before tests.
- Automate OTA updates with `expo publish --release-channel production` guarded by rollout percentage.