Project Setup Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important best practices drawn from every page in this section.
Scaffold with an SDK template pin: npx create-expo-app@latest --template default@sdk-57 - upgrading later costs more than starting on the supported SDK.
Run expo-doctor on day one: Fix version skew before the first feature PR - doctor catches config and dependency mismatches early.
Commit lockfiles with the app: CI and EAS must resolve the same tree as local dev - floating installs hide native/JS skew until TestFlight.
Keep package.json scripts boring: start, ios, android, lint, typecheck - every engineer should not memorize one-off Metro flags.
Extend expo/tsconfig.base: Strict TypeScript from the template - custom tsconfig drift breaks Expo Router typed routes and autolinking types.
Use app.config.ts for environments: Dynamic config beats duplicated app.json files - drive variants with APP_VARIANT or BRAND env vars.
Pick the smallest template that fits: blank-typescript for learning; tabs only when navigation is day-one scope - delete unused boilerplate immediately.
Organize by feature, not layer-only: features/orders/ owns screens, hooks, and presenters - global components/ is for design-system primitives only.
One public export per feature: Route files import OrdersScreen from features/orders - no cross-feature deep imports into hooks/useOrder.ts.
Thin app/ directory: Expo Router files re-export feature screens - business logic does not live beside _layout.tsx.
Start monorepos with pnpm + Turborepo when sharing UI: apps/mobile + packages/ui with workspace:* - add Nx when generators and affected graphs justify the complexity.
Let SDK 57 configure Metro first: Automatic monorepo detection beats hand-written watchFolders - customize only when doctor and builds prove a gap.
Hoist or isolate pnpm symlinks consciously: nodeLinker: hoisted avoids many Metro symlink bugs - document the choice in README for onboarding.
Never duplicate react-native in the graph: Two native copies cause "Invalid hook call" and obscure redboxes - npx expo-doctor and why react-native checks in CI.
Declare peerDependencies in shared packages: Workspace libraries should peer react, react-native, and expo - apps provide the single installed version.
Use Nx tags for boundaries: scope:orders cannot import scope:billing internals - enforce with @nx/enforce-module-boundaries in growing orgs.
Run affected tasks in CI: nx affected -t lint,typecheck,test keeps monorepo PRs fast - full-repo runs do not scale past a dozen packages.
White-label with config, not forks: apps/acme and apps/globex share packages/core - brand tokens inject via BrandProvider, not copy-paste screens.
Separate EAS projectId per app binary: White-label flavors need distinct credentials, env vars, and update channels - one projectId per bundle ID.
Gitignore native folders under CNG: Committed ios//android/ block SDK upgrades - regenerate on EAS Build or expo prebuild --clean.
Document first-run verification: A script that runs expo-doctor, tsc --noEmit, and a dev-client build - new hires prove the toolchain in one command.
Ignore .env.local, commit .env.example: Machine-specific secrets stay local - example env documents required EXPO_PUBLIC_* keys without values.
Align Babel and Metro with the template: Custom plugins belong in documented config - silent Babel drift breaks Fast Refresh and Reanimated.
Choose Turborepo or Nx, not both orchestrators: One task graph owner - mixing confuses dev scripts and cache keys.
Revisit folder structure at ~15 engineers: Layer-only layouts collapse under parallel feature teams - schedule a migration before import cycles force a rewrite.
app.config and EAS project.react-native instance.Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 16, 2026