Expo + Expo Router + GitHub Actions CI/CD + App Store & Play Store deploy.
Build your app. Push to deploy.
English | νκ΅μ΄
Part of Starter Series β Stop explaining CI/CD to your AI every time. Clone and start.
Docker Deploy Β· Discord Bot Β· Telegram Bot Β· Browser Extension Β· Electron App Β· npm Package Β· React Native Β· VS Code Extension Β· MCP Server Β· Python MCP Server Β· Cloudflare Pages
Via create-starter (recommended):
npx @starter-series/create my-app --template react-native
cd my-app && npm install && npx expo startOr clone directly:
git clone https://github.com/starter-series/react-native-starter my-app
cd my-app && npm install && npx expo startThen scan the QR code with Expo Go (or press a for Android / i for iOS).
βββ app/ # Expo Router app directory
β βββ _layout.js # Root layout β wraps everything in <AuthProvider>
β βββ (app)/ # Auth-gated group (redirects to /login if signed out)
β β βββ _layout.js # Guard layout
β β βββ index.js # Home screen
β β βββ about.js # Example second screen
β β βββ profile.js # Signed-in user info + sign-out
β βββ (auth)/
β βββ _layout.js # Bounces to / if already signed in
β βββ login.js # "Continue with Google" screen
βββ lib/
β βββ auth-context.js # AuthProvider + useAuth + handleAuthResult
β βββ env.js # Reads EXPO_PUBLIC_GOOGLE_* client IDs
βββ assets/ # App icon, splash, adaptive icon
βββ tests/ # Jest tests (auth-context, screens, structure)
βββ .env.example # Google OAuth client ID placeholders
βββ .github/
β βββ workflows/
β β βββ ci.yml # Lint, test, audit
β β βββ cd-android.yml # Build + submit to Play Store via EAS
β β βββ cd-ios.yml # Build + submit to App Store via EAS
β β βββ setup.yml # Auto setup checklist on first use
β βββ PULL_REQUEST_TEMPLATE.md
βββ docs/
β βββ EXPO_SETUP.md # Expo account + EAS setup
β βββ APP_STORE_SETUP.md # Apple Developer + App Store Connect
β βββ PLAY_STORE_SETUP.md # Google Play Console setup
β βββ PRIVACY_MANIFEST.md # iOS PrivacyInfo.xcprivacy + Android photo picker permission
βββ scripts/
β βββ bump-version.js # Bumps version in app.json + package.json
βββ eas-hooks/
β βββ eas-build-pre-install.sh # Example EAS Build hook
βββ eslint.config.js # ESLint v9 flat config
βββ app.json # Expo config
βββ eas.json # EAS Build profiles
βββ package.json
- Expo + Expo Router -- file-based routing, no native toolchain required locally
- CI Pipeline -- security audit, lint, test on every push and PR
- CD Pipeline -- one-click deploy to App Store and Play Store via EAS Build
- Cloud builds -- EAS compiles native binaries in the cloud (no local Xcode/Android Studio needed)
- Version management --
npm run version:patch/minor/majorto bumpapp.json - Starter code -- Home + About + Profile screens, Google OAuth login wired in
- Store setup guides -- step-by-step docs for Apple Developer, Google Play Console, and EAS
- Template setup -- auto-creates setup checklist issue on first use
| Step | What it does |
|---|---|
| Security audit | npm audit for dependency vulnerabilities |
| Lint | ESLint on app and component code |
| Test | Jest with React Native Testing Library |
| Workflow | What it does |
|---|---|
CodeQL (codeql.yml) |
Static analysis for security vulnerabilities (push/PR + weekly) |
Maintenance (maintenance.yml) |
Weekly CI health check β auto-creates issue on failure |
Stale (stale.yml) |
Labels inactive issues/PRs after 30 days, auto-closes after 7 more |
CHANGELOG (update-changelog.yml) |
Appends merged-PR entries to CHANGELOG.md automatically |
| Step | What it does |
|---|---|
| CI | Runs full CI first |
| Version guard | Fails if git tag already exists for this version |
| EAS Build | eas build --platform android --profile production |
| EAS Submit | Uploads AAB to Play Store (internal track) |
| GitHub Release | Creates a tagged release |
| Step | What it does |
|---|---|
| CI | Runs full CI first |
| Version guard | Fails if git tag already exists for this version |
| EAS Build | eas build --platform ios --profile production |
| EAS Submit | Uploads to App Store Connect |
| GitHub Release | Creates a tagged release |
How to deploy:
- Set up
EXPO_TOKENsecret (see below) - Configure store accounts (see docs/)
- Run
eas submit:configureonce to populate thesubmitblock ineas.jsonwith your Apple/Google credentials - Bump version:
npm run version:patch - Go to Actions tab -> Deploy to Play Store or Deploy to App Store -> Run workflow
| Secret | Description |
|---|---|
EXPO_TOKEN |
Expo access token for EAS CLI authentication |
See docs/EXPO_SETUP.md for how to generate the token.
Store credentials are configured through eas.json and eas credentials -- not GitHub Secrets. See:
- docs/APP_STORE_SETUP.md for iOS
- docs/PLAY_STORE_SETUP.md for Android
Run custom scripts at different stages of the EAS Build lifecycle. Scripts at eas-hooks/<name>.sh are auto-discovered by EAS CLI β no eas.json config needed. An example eas-build-pre-install.sh is included to get you started.
| Hook | When it runs | Use for |
|---|---|---|
eas-build-pre-install.sh |
Before npm install |
Inject env vars, validate required secrets, write .env files |
eas-build-post-install.sh |
After npm install, before prebuild |
patch-package, native module config, Ruby/CocoaPods tweaks |
eas-build-on-success.sh |
After successful build | Notifications, uploading extra artifacts |
eas-build-on-error.sh |
On build failure | Error reporting, debugging output |
eas-build-on-cancel.sh |
On cancellation | Cleanup |
Scripts must be executable (chmod +x) and committed with the executable bit set. Use eas secret:create to expose secrets to hooks as env vars.
Docs: Run custom scripts with npm hooks
# Start dev server
npx expo start
# Run on specific platform
npm run android
npm run ios
# Bump version (updates app.json + package.json)
npm run version:patch # 1.0.0 -> 1.0.1
npm run version:minor # 1.0.0 -> 1.1.0
npm run version:major # 1.0.0 -> 2.0.0
# Lint & test
npm run lint
npm test| This template | Starting from scratch | |
|---|---|---|
| CI/CD | Full pipeline included | Set up yourself |
| EAS config | Pre-configured build profiles | Read docs, trial and error |
| Store deployment | One-click via GitHub Actions | Manual eas build + eas submit each time |
| Version management | npm run version:patch auto-bumps |
Edit app.json by hand |
| Setup guides | Step-by-step docs included | Scattered across Expo docs, Apple docs, Google docs |
| AI/vibe-coding | LLMs generate clean Expo code | LLMs must understand your custom setup |
| Time to first deploy | Minutes (after store account setup) | Hours of configuration |
The key insight: EAS handles native builds in the cloud -- no local Xcode or Android Studio needed for CI/CD. This template wires that up to GitHub Actions so you get one-click deploys from day one.
Google sign-in is built in with expo-auth-session + expo-secure-store. Routes under app/(app)/ are gated β signed-out users get bounced to /login. Tokens live in the iOS Keychain / Android Keystore, never AsyncStorage.
Setup:
- Google Cloud Console β Credentials β Create OAuth client ID. Create three:
- Web application (required β used by the Expo AuthSession proxy in dev)
- iOS β bundle ID =
ios.bundleIdentifierfromapp.json - Android β package =
android.package, SHA-1 from your keystore (eas credentialswill show it)
cp .env.example .envand paste the client IDs:EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=... EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=... EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID=...npx expo startand tap Continue with Google.
Without client IDs, the login button surfaces a clear error message β it won't crash. Swap providers by replacing expo-auth-session/providers/google inside lib/auth-context.js.
Docs: Expo Google authentication guide.
This template uses JavaScript to stay lightweight. To add TypeScript:
- Add
typescriptand@types/reactto devDependencies - Add a
tsconfig.json - Rename
.jsfiles to.tsx
Expo supports TypeScript out of the box -- no extra configuration needed.
- Cloud-native builds. EAS compiles native binaries off-device so CI/CD runs without local Xcode or Android Studio.
- Auth gating via route groups.
app/(app)/is the protected zone β there is no "auth check" scattered across screens. - Secrets in the OS keychain. Tokens go to iOS Keychain / Android Keystore through
expo-secure-store, neverAsyncStorage. - Lint, test, audit on every push. Supply-chain hardening (
--ignore-scripts, pinned gitleaks, CodeQL) is on by default β not an afterthought.
- TypeScript by default. Stays JS to keep the template small; opt-in steps are documented above.
- Custom native modules. Anything requiring
expo prebuild+ native code is out of scope. Use a bare workflow if you need it. - Backend. This is the client only. Pair with a separate API repo.
- State management library. No Redux/Zustand/etc. β the auth context is the only global state shipped.
PRs welcome. Please use the PR template.