React Native + Expo Rules
Expo project rules covering typed expo-router navigation, platform-split files, asset and font handling, and list performance.
Use when: Use in Expo SDK 50+ apps with expo-router and TypeScript to keep navigation type-safe and platform-specific code contained.
The skill file
Cursor · .cursorrules.cursorrules3493 chars
You are working in an Expo (managed workflow) app with expo-router and strict TypeScript.
## Navigation
- expo-router file-based routes only — never install or generate raw `@react-navigation` stack/tab setup by hand; expo-router wraps it.
- Typed routes are on (`experiments.typedRoutes: true` in app.json). Navigate with `<Link href="/profile/[id]" />` or `router.push({ pathname: "/profile/[id]", params: { id } })` — never a template-string path, which bypasses route typing.
- Read params with `useLocalSearchParams<{ id: string }>()` and validate/coerce immediately; params are always strings.
- Route groups organize layout, not URLs: auth screens under `app/(auth)/`, tabs under `app/(tabs)/`. Screens private to a route live in a co-located `components/` folder outside `app/` (only routes live in `app/`).
- Auth gating happens in a layout via `<Redirect href="/sign-in" />` after the session loads — never `router.replace` inside a screen's `useEffect`.
## Platform splits
- One `Platform.OS` ternary in a file is fine. Two or more, or any platform-specific JSX block, means split the file: `haptics.ios.ts` / `haptics.android.ts` with a shared `haptics.d.ts` (or a common `.ts` fallback) so imports stay `./haptics`.
- Platform-specific styling constants (shadows vs elevation, status-bar padding) live in one `theme/platform.ts`, computed once with `Platform.select` — not inline `Platform.select` scattered through StyleSheets.
- Never assume safe-area insets: wrap screens in `SafeAreaView` from `react-native-safe-area-context` (not the core one) or use `useSafeAreaInsets()` for fine control.
- Web is a target too: no direct `window`/`document` access outside `.web.ts` files; check `Platform.OS === "web"` before native-only modules.
## Assets, fonts, images
- Static assets are imported (`require("@/assets/images/logo.png")`) so Metro bundles and hashes them — never hardcoded file paths or remote URLs for things that ship with the app.
- Use `expo-image` (`<Image>` with `contentFit`, `transition`, `cachePolicy`) instead of core `Image` everywhere. Give every remote image an explicit size or flex constraint; an unsized image is a layout bug.
- Fonts load via `expo-font` config plugin in app.json (embedded at build time), not runtime `useFonts`, unless the font is genuinely dynamic.
- Icons come from `@expo/vector-icons` only; no ad-hoc icon PNGs.
## Components and style
- `StyleSheet.create` at file bottom, or NativeWind classes if the project has it — never inline style objects in JSX (they re-allocate every render), except a single dynamic value merged as `[styles.box, { height }]`.
- Long lists: `FlashList` (or `FlatList` with `keyExtractor` returning a stable id). Never render a list by `.map` inside a `ScrollView` past ~10 items.
- Touchables: `Pressable` with `hitSlop` of at least 8 on small targets; no deprecated `TouchableOpacity` in new code.
- Keyboard handling on every form screen: `KeyboardAvoidingView` with `behavior={Platform.OS === "ios" ? "padding" : "height"}`, plus `keyboardShouldPersistTaps="handled"` on the enclosing scroll view.
## Config and native modules
- Secrets and environment switches go through `app.config.ts` `extra` + `expo-constants` (or `EXPO_PUBLIC_*` vars for client-safe values) — never a committed `.env` read at runtime.
- Any library with native code must be Expo-compatible (config plugin or included in the Expo SDK). Adding one means a new dev client build — call that out in the PR description.
Install
drop into your repoSave this to your project or home directory so Cursor can load it.
path./.cursorrules
Discussion
What people are saying
Forks
0 forks of this skillLoading forks…