Create or Clone a Project
Starting a new project
pnpm create expo-app my-app # or: npx create-expo-app@latest my-app
cd my-app && pnpm install
This gives you a modern Expo app (SDK 54 / RN 0.81 style) with TypeScript and file-based routing ready to go.
Joining an existing project
git clone <repo-url>
cd <repo>
pnpm install
A note on native folders
The team default is the managed workflow with Continuous Native Generation
(CNG): the ios/ and android/ folders are never committed. They’re
generated on demand — locally with npx expo prebuild, or transparently by
EAS Build — from app.config.ts and your installed packages. See Getting
Started → Managed Workflow (CNG) for the full rationale and what to do when
a native SDK needs more than a config plugin provides.
Git hygiene: your .gitignore
create-expo-app ships a solid .gitignore for the managed workflow —
this guide’s default below builds on it by ignoring /ios and /android
outright, not just their build subfolders. If a project deviates from that
default and commits the native folders anyway, it also inherits their
build outputs, which are easy to miss and bloat every clone with hundreds
of MB of compiled artifacts.
Builds never belong in the repo. Compiled apps (*.apk, *.aab, *.ipa)
and dependency caches (node_modules/) are all generated — regenerated from
source on every build. Committing them bloats history irreversibly.
Make sure your .gitignore covers at least the following:
# Dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native folders — generated by CNG, never commit (see Managed Workflow)
/ios
/android
# Compiled app binaries — generated on every build
*.apk
*.aab
*.ipa
*.app
*.dSYM
*.dSYM.zip
# Metro / bundler
.metro-health-check*
*.jsbundle
# Signing material & secrets — must never be committed
*.p8
*.p12
*.key
*.mobileprovision
.env
.env.*
!.env.example
# Logs & debug output
npm-debug.*
yarn-debug.*
yarn-error.*
*.log
# TypeScript
*.tsbuildinfo
# IDE / OS noise
.DS_Store
.vscode/
.idea/
*.pem
A few notes on the entries that trip people up:
- No Android debug keystore to commit. Under the managed default,
android/isn’t committed at all, so there’s no shareddebug.keystorefile to carve an exception for. Each machine (and EAS) uses its own debug signature; if a feature genuinely needs a shared debug SHA-1 (e.g. Google Sign-In), manage it as an EAS credential, not a committed file. Your release keystore is always a secret regardless of workflow: keep it out of the repo (store it in EAS or a secrets manager). - The
.env.*files (.env.dev,.env.preview,.env.prod) are ignored;.env.exampleis committed. See Environment Variables and Environment & Configuration → Secrets for the full rule — and rotate anything that leaks into git history. - Firebase config (
google-services.json,GoogleService-Info.plist): these can’t live insideios//android/under the managed default (not committed), so point to them from outside — e.g. a root-level path referenced viagoogleServicesFileinapp.config.ts, which a config plugin copies into the generated project atprebuildtime. Whether that file itself is safe to commit (it’s usually a non-secret per-project identifier) is a decision to make once per project and document in the repo’sREADME.
If ios/, android/, or a build artifact is already tracked, adding it to
.gitignore won’t remove it — Git keeps ignoring changes only to untracked
files. Stop tracking it with git rm -r --cached ios android (adjust paths for
a narrower cleanup), then commit.
Next: set up your Environment Variables.