A production-ready Expo React Native template with semantic versioning, CI/CD workflows, modular monolith architecture, and dynamic configuration.
git clone https://github.com/Softaims/expo-rn-template.git your-app-name
cd your-app-name
rm -rf .gitRun the reset script to configure the project with your app name. This will:
- Update
package.jsonwith your app name and reset version to1.0.0 - Update
app.config.tswithAPP_NAME,SLUG, andSCHEME - Reset
CHANGELOG.md
npm run reset -- your-app-nameOr directly:
npx tsx scripts/reset.ts your-app-nameAfter running the reset script, manually update these fields in app.config.ts:
const BUNDLE_IDENTIFIER = "com.yourcompany.yourapp"; // iOS bundle identifier
const PACKAGE_NAME = "com.yourcompany.yourapp"; // Android package namenpm installBefore pushing your first commit, create both main and production branches. This is required for semantic-release to work properly.
git init
git add .
git commit -m "chore: initial commit"
git branch -M main
git remote add origin https://github.com/your-username/your-repo.git
# Create production branch (required for semantic-release)
git checkout -b production
git push -u origin production
# Switch back to main and push
git checkout main
git push -u origin mainImportant: The
productionbranch must exist before pushing tomain, otherwise semantic-release will fail.
npx expo startYou can open the app in:
When using this template for a new project, you must configure the following:
Create a .env file with the following variables:
| Variable | Description |
|---|---|
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY |
Clerk authentication publishable key |
SENTRY_AUTH_TOKEN |
Sentry authentication token for error tracking |
This project uses dynamic configuration via app.config.ts instead of a static app.json. This allows for:
- Environment-based configuration
- Computed values (like build numbers from version)
- TypeScript type safety
Update these fields in app.config.ts before starting development:
// App configuration
const APP_NAME = "your-app-name"; // Display name of your app
const SLUG = "your-app-slug"; // URL-friendly identifier
const SCHEME = "your-app-scheme"; // Deep linking scheme
const BUNDLE_IDENTIFIER = "com.your.app"; // iOS bundle identifier
const PACKAGE_NAME = "com.your.app"; // Android package name
// SENTRY configuration
const SENTRY_PROJECT = "your-sentry-project";
const SENTRY_ORGANIZATION = "your-sentry-org";These can be configured when you're ready to deploy:
// EAS configuration
const EAS_PROJECT_ID = "your-eas-project-id";
const EAS_PROJECT_OWNER = "your-eas-owner"; // For EAS Organization onlyThis project uses semantic-release with Conventional Commits for automated versioning.
- Development (main):
X.Y.Z-alpha.N(e.g.,1.2.0-alpha.1,1.2.0-alpha.2) - Production:
X.Y.Z(e.g.,1.2.0,1.3.0,2.0.0)
| Commit Type | Description | Version Bump |
|---|---|---|
feat: |
New feature | Minor (1.0.0 → 1.1.0) |
fix: |
Bug fix | Patch (1.0.0 → 1.0.1) |
perf: |
Performance improvement | Patch |
refactor: |
Code refactoring | Patch |
feat!: or BREAKING CHANGE: |
Breaking change | Major (1.0.0 → 2.0.0) |
docs: |
Documentation only | No release |
chore: |
Maintenance tasks | No release |
style: |
Code style changes | No release |
test: |
Test changes | No release |
┌──────────────────────────────────────────────────────────────────────────────┐
│ DEVELOPMENT LIFECYCLE │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ main (development branch) production (stable releases) │
│ ───────────────────────── ─────────────────────────── │
│ │
│ feat: initial setup │
│ └── 1.0.0-alpha.1 │
│ │
│ fix: auth bug │
│ └── 1.0.0-alpha.2 │
│ │
│ feat: add dashboard │
│ └── 1.1.0-alpha.1 │
│ │ │
│ └──────── PR: main → production ────────► 1.1.0 (first release) │
│ │ │
│ ◄─────────────────── git pull ─────────────────────────┘ │
│ │
│ fix: dashboard crash │
│ └── 1.1.1-alpha.1 │
│ │
│ feat: user profiles │
│ └── 1.2.0-alpha.1 │
│ │
│ fix: profile image │
│ └── 1.2.0-alpha.2 │
│ │ │
│ └──────── PR: main → production ────────► 1.2.0 │
│ │ │
│ ◄─────────────────── git pull ─────────────────────────┘ │
│ │
│ feat!: redesign API (BREAKING) │
│ └── 2.0.0-alpha.1 │
│ │
│ feat: notifications │
│ └── 2.1.0-alpha.1 │
│ │ │
│ └──────── PR: main → production ────────► 2.1.0 │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
Here's a detailed walkthrough of how versions evolve through the development lifecycle:
| Step | Branch | Commit | Resulting Version | Notes |
|---|---|---|---|---|
| 1 | main |
feat: add authentication |
1.0.0-alpha.1 |
First feature, alpha release |
| 2 | main |
fix: login validation |
1.0.0-alpha.2 |
Patch doesn't bump minor |
| 3 | main |
feat: add home screen |
1.1.0-alpha.1 |
New feature bumps minor |
| 4 | main |
fix: home screen layout |
1.1.0-alpha.2 |
Another alpha iteration |
| 5 | main |
docs: update readme |
— | No release (docs) |
| Step | Action | Result |
|---|---|---|
| 6 | Create PR: main → production |
— |
| 7 | Merge PR | production releases 1.1.0 |
| 8 | Merge production → main |
Sync stable version back to main |
| 9 | git pull on local main |
Sync version to local |
| Step | Branch | Commit | Resulting Version | Notes |
|---|---|---|---|---|
| 10 | main |
fix: critical bug |
1.1.1-alpha.1 |
Patch version for fix |
| 11 | main |
perf: optimize queries |
1.1.2-alpha.1 |
Performance = patch |
| 12 | main |
feat: user settings |
1.2.0-alpha.1 |
Feature bumps minor |
| 13 | main |
refactor: clean code |
1.2.1-alpha.1 |
Refactor = patch |
| Step | Action | Result |
|---|---|---|
| 14 | Merge PR to production |
production releases 1.2.1 |
| 15 | Merge production → main |
Sync stable version back to main |
| 16 | git pull on local main |
Sync version to local |
| Step | Branch | Commit | Resulting Version | Notes |
|---|---|---|---|---|
| 17 | main |
feat!: new API structure |
2.0.0-alpha.1 |
Breaking change = major |
| 18 | main |
feat: dark mode |
2.1.0-alpha.1 |
Feature bumps minor |
| 19 | main |
fix: theme toggle |
2.1.0-alpha.2 |
Patch in alpha |
| Step | Action | Result |
|---|---|---|
| 20 | Merge PR to production |
production releases 2.1.0 |
| 21 | Merge production → main |
Sync stable version back to main |
- All development happens on
main - Each qualifying commit triggers the Development CI workflow
- Versions are tagged as alpha pre-releases:
1.0.0-alpha.1,1.0.0-alpha.2, etc. - Alpha releases increment automatically with each commit
Important: After pushing, semantic-release bot creates a new commit with updated
package.json,package-lock.json, andCHANGELOG.md. Always rungit pullto sync your local repository before continuing development.
When ready to release:
- Create a Pull Request from
main→production - Merge the PR
- Production CI workflow runs automatically
- Semantic-release creates a stable version (removes
-alpha.Nsuffix) - A GitHub release is created with changelog
Production workflow supports:
- OTA updates: For code-only changes (use "OTA" label on PR)
- Full builds: For native changes (use "BUILD" label or default)
- Manual trigger: Via workflow_dispatch
After every production release, you must merge production back into main to sync the stable version:
# On main branch
git checkout main
git pull origin main
# Merge production into main
git merge origin/production
# Push the sync
git push origin mainOr create a PR from production → main and merge it.
Why is this required? Semantic-release creates a commit on
productionwith the stable version (e.g.,1.1.0). Without merging back,mainwon't have this version update, causing version conflicts and incorrect alpha versioning in subsequent releases.
After syncing:
- Continue development on
main - Next commits will create new alpha versions based on the synced stable version
- Example: After syncing
1.1.0, next feature commit creates1.2.0-alpha.1
| Event | Action Required |
|---|---|
Push to main |
git pull origin main (semantic-release creates alpha version commit) |
Merge to production |
Merge production → main (sync stable version back) |
After pushing to main:
git push origin main
# Wait for CI to complete...
git pull origin mainAfter production release:
git checkout main
git pull origin main
git merge origin/production
git push origin mainThis ensures your local repository and main branch stay in sync with all version commits.
Build numbers are automatically calculated from the version using the format MMNNPP0000:
M= Major version (2 digits)N= Minor version (2 digits)P= Patch version (2 digits)
Examples:
1.2.3→1020300007.17.10→717100000
This project follows the Modular Monolith design pattern - an all-in-one project where each feature lives in a separate module/shell.
Reference: Modular Monolith: A disruptive guide to architecting your React app
- Scalable: Can scale both up and down with team size
- Low complexity: Easy onboarding for new team members
- No conflicts: Multiple teams can work without stepping on each other
- Single deployment: One CI/CD pipeline, easy to maintain
- Clear ownership: Each module has defined boundaries
├── app/ # Routing layer only (Expo Router)
│ ├── _layout.tsx
│ ├── (auth)/
│ │ ├── login/
│ │ ├── signup/
│ │ ├── forgotPassword/
│ │ └── ...
│ ├── (tabs)/
│ │ ├── (settings)/
│ │ └── ...
│ └── (legal)/
│
├── modules/ # Feature modules (business logic)
│ ├── auth/
│ │ ├── screens/ # Screen components
│ │ ├── components/ # UI components
│ │ ├── hooks/ # Custom hooks
│ │ ├── config/ # Configuration
│ │ ├── schemas/ # Validation schemas
│ │ ├── types.ts # TypeScript types
│ │ └── index.ts # Public exports
│ ├── settings/
│ ├── chat/
│ ├── notifications/
│ ├── legal/
│ ├── splash/
│ ├── sentry/
│ └── commons/ # Shared components across modules
| Directory | Responsibility |
|---|---|
app/ |
Routing only - File-based routing with Expo Router. Minimal logic, just imports screens from modules. |
modules/ |
Feature logic - Each module contains its own screens, components, hooks, configs, and types. Modules are self-contained and don't share internal logic. |
modules/commons/ |
Shared utilities - Components or logic needed across multiple modules. |
Each module follows a consistent structure:
modules/[feature]/
├── screens/ # Screen components (imported by app/ routes)
├── components/ # Feature-specific UI components
├── hooks/ # Custom React hooks
├── config/ # Feature configuration
├── schemas/ # Zod/Yup validation schemas
├── types.ts # TypeScript interfaces/types
└── index.ts # Public API exports