A production-ready Expo SDK 54 + React Native 0.81 starter.
Use this template to bootstrap new mobile apps with a battle-tested tech stack, sensible defaults, and example features (database, i18n, theming, settings flow) so you can focus on the product, not the wiring.
- Framework: Expo SDK 54, React Native 0.81.5, React 19.1.0
- Routing: Expo Router (file-based, typed routes)
- Database: expo-sqlite with migrations and typed query layer
- State: Zustand stores
- i18n: i18n-js + AsyncStorage persistence (zh-CN, en)
- Theming: Centralized theme tokens (colors, spacing, radii, type, shadow)
- Animations: react-native-reanimated 4 + react-native-worklets 0.5
- Testing: jest + jest-expo + @testing-library/react-native, with mocks
- EAS: eas.json profiles for
development/preview/production - Type safety: TypeScript strict mode + path aliases
# Option A — copy the folder
cp -R mobile-react-native-template/ my-new-app/
cd my-new-app
# Option B — use as a GitHub template repository (after pushing)
gh repo create my-new-app --template=mobile-react-native-templateSearch and replace these placeholders across the codebase:
| Placeholder | Replace with | Where |
|---|---|---|
App Template |
My App |
app.json, android/settings.gradle |
app-template |
my-app |
app.json (slug, scheme) |
com.example.app |
com.yourcompany.app |
app.json, android/app/build.gradle |
mobile-react-native-template |
my-app |
package.json |
app-template.db |
my-app.db |
database/index.ts |
EAS projectId |
your EAS project UUID | app.json (extra.eas.projectId) |
Android app_name |
My App |
android/app/src/main/res/values/strings.xml |
Then regenerate native projects:
npx expo prebuild --cleannpm install
npx expo start # dev server
npx expo start --android # Android
npx expo start --ios # iOS
npm test # unit testsnpm install -g eas-cli
eas login
eas build:configure # one-time, links to your EAS project
eas build --profile preview # internal testing build
eas build --profile production # store build.
├── app/ # Expo Router pages (file-based routing)
│ ├── (tabs)/ # Bottom tab navigation
│ ├── settings/ # Settings pages (with sub-routes)
│ └── _layout.tsx # Root layout
│
├── assets/ # Images, icons, splash
│
├── components/ # Reusable UI components
│ ├── ui/ # Generic UI primitives
│ ├── icons/ # SVG icon system
│ └── ... # Feature-specific components
│
├── constants/ # Constants and tokens
│ ├── theme.ts # Theme tokens (colors/spacing/type)
│ ├── i18n.ts # i18n configuration
│ └── locales/ # Translation files (zh-CN.json, en.json)
│
├── database/ # SQLite database layer
│ ├── schema.ts # Table definitions
│ ├── migrations.ts # Version management
│ ├── queries/ # Typed CRUD per entity
│ └── index.ts # Database singleton
│
├── hooks/ # Custom React hooks
│
├── services/ # External integrations (AI, storage, etc.)
│
├── stores/ # Zustand stores
│
├── types/ # TypeScript types/interfaces
│
├── utils/ # Pure utility functions
│
├── __mocks__/ # Jest module mocks
├── __tests__/ # Unit tests
│
├── android/ # Native Android project
├── scripts/ # Build & maintenance scripts
├── docs/ # Design & development docs
│
├── app.json # Expo configuration
├── eas.json # EAS Build profiles
├── babel.config.js # Babel config (reanimated/worklets)
├── metro.config.js # Metro bundler config
├── tsconfig.json # TypeScript with path aliases
├── jest.config.js # Jest config (mirrors tsconfig paths)
└── jest.setup.js # Jest setup (mocks, polyfills)
Mirrored across tsconfig.json and jest.config.js:
@/* → ./*
@components/* → ./components/*
@constants/* → ./constants/*
@database/* → ./database/*
@hooks/* → ./hooks/*
@services/* → ./services/*
@stores/* → ./stores/*
@types/* → ./types/*
@utils/* → ./utils/*import { theme } from '@/constants/theme'
// Colors
<View style={{ backgroundColor: theme.colors.primary.main }} />
// Spacing
<View style={{ padding: theme.spacing.base }} />
// Typography
<Text style={{ fontSize: theme.fontSize.body, fontWeight: theme.fontWeight.semibold }} />Tokens: theme.colors, theme.spacing, theme.borderRadius, theme.fontSize,
theme.fontWeight, theme.shadow.
import { useI18n } from '@/hooks/useI18n'
function MyComponent() {
const { t, locale, setLocale } = useI18n()
return <Text>{t('home.title')}</Text>
}- Translation files:
constants/locales/zh-CN.json,constants/locales/en.json - Add a new language by dropping a
xx.jsonfile in the same dir. - Use dot-notation keys, grouped by feature (
settings.ai.title,common.ok). - User choice persists in AsyncStorage.
Rule: Never hardcode user-facing text. Always go through t('key').
import { database } from '@/database'
import { dietQueries } from '@/database/queries'
await database.initialize()
const today = await dietQueries.getToday()Conventions:
- Schema lives in
database/schema.ts. Migrations indatabase/migrations.ts. - Typed queries live in
database/queries/<entity>.tsand export a namespace (dietQueries,weightQueries, ...). - All queries return typed results — no
any.
npm test # run once
npm run test:watch # watch mode
npm run test:ci # CI mode (--ci --runInBand)Conventions:
- Test files:
**/__tests__/**/*.test.(ts|tsx). - Module mocks: drop a file in
__mocks__/<module>.js(Jest auto-loads). - Inline mocks: add
jest.mock(...)tojest.setup.jsfor cross-cutting modules.
| Task | Command |
|---|---|
| Start dev server | npx expo start |
| Clear Metro cache | npx expo start --clear |
| Regenerate native projects | npx expo prebuild --clean |
| Run on Android | npx expo run:android |
| Run on iOS | npx expo run:ios |
| Lint / type check (manual) | npx tsc --noEmit |
| Unit tests | npm test |
| EAS internal build | eas build --profile preview |
| EAS production build | eas build --profile production |
See docs/ for deeper material (architecture, design, database, build, release
checklist). Treat docs/progress/ as your development journal — add a dated
entry per iteration.
MIT.