Skip to content

About

React Native + Expo template for iOS and Android with strict TypeScript, routing, validated APIs, themes, tests, and CI. Includes Claude Code conventions: shared instructions, scoped rules, skills, review/test agents, and a formatting hook.

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

📱 React Native App Template

A practical starting point for iOS and Android, built with Expo and TypeScript.

Feature-based architecture, validated API data, light and dark themes, and automated checks.

Expo SDK 58 TypeScript Platforms License: MIT PRs Welcome

Get started · Explore the structure · Customize your app · Contribute · Get help


✨ What’s included

This template brings Expo Router, TanStack Query, zustand, and zod together with strict TypeScript, Jest, ESLint, Prettier, and GitHub Actions. Shared agent instructions and Claude Code configuration are included for teams using coding assistants.

The example Posts feature includes a list and detail screen backed by JSONPlaceholder. Settings persists a light, dark, or system theme. Both provide working patterns to adapt for your product; the template app identity remains TemplateApp until you rename it.

Contents

Stack

Concern Choice
Framework Expo SDK 58 (React Native 0.88, React 19.3)
Language TypeScript 6.0, strict + noUncheckedIndexedAccess
Navigation Expo Router (file-based, typed routes)
Server state / API TanStack Query 5 + fetch wrapper (src/lib/api/client.ts) + zod 4 validation
Client state zustand 5, persisted with AsyncStorage
Theming Design tokens in src/theme/tokens.ts, light and dark palettes
Testing Jest 29 (jest-expo preset) + @testing-library/react-native 14
Lint / format ESLint 9 (eslint-config-expo flat config) + Prettier 3, eslint-config-prettier
Builds Continuous Native Generation (expo prebuild) locally; EAS Build for releases
CI GitHub Actions: typecheck, lint, format, tests, expo-doctor, prebuild

Why Expo instead of the bare React Native CLI. The React Native docs recommend starting new apps with a framework and name Expo (reactnative.dev/docs/environment-setup). Expo provides file-based routing, SDK-matched dependency versions (npx expo install), config plugins for native setup, and cloud builds. Native code stays fully available: npm run prebuild generates standard Xcode and Gradle projects, and any React Native library with native code works in a development build. The trade-off is that native changes go through app.config.ts and config plugins rather than direct edits (see Native folders).

The dependency versions are declared in package.json and locked in package-lock.json. Use npx expo install for SDK-compatible additions and npm run doctor to check your local dependency and configuration health.

Prerequisites

  • Node.js 22.13 or newer in the 22.x line (.nvmrc; nvm use). Expo SDK 58 supports ^22.13.0 || ^24.3.0 || ^26.0.0.
  • npm 10 (ships with Node 22). The lockfile is package-lock.json.
  • iOS: macOS with Xcode and an iOS Simulator, or an EAS account for cloud builds.
  • Android: Android Studio with an SDK and an emulator, or an EAS account for cloud builds.
  • Full environment guide: docs.expo.dev/get-started/set-up-your-environment.

Setup

  1. Create your repository from this template (GitHub Use this template), or clone it.

  2. Install dependencies and create your env file:

    nvm use
    npm ci
    cp .env.example .env
  3. Rename the template: see Rename checklist. You can do this later, but bundle IDs are hard to change after a store release.

  4. Run on a simulator or device. This builds a development build (includes expo-dev-client):

    npm run ios       # macOS + Xcode
    npm run android   # Android SDK + emulator or device

    After the first build, start only the JS bundler with npm start.

    Without local native tooling, build the development client in the cloud instead: npx eas-cli@latest build --profile development --platform ios|android.

  5. Before committing: npm run check.

The app fails at startup with a clear message if EXPO_PUBLIC_API_BASE_URL is missing or not a valid http(s) URL.

Scripts

Script What it does
npm start Start Metro for a development build
npm run ios Generate ios/ if needed, compile, install and launch on simulator
npm run android Generate android/ if needed, compile, install and launch
npm run prebuild Regenerate ios/ and android/ from app.config.ts (--clean)
npm run typecheck tsc --noEmit
npm run lint ESLint, fails on any warning
npm run lint:fix ESLint with autofix
npm run format Prettier write
npm run format:check Prettier check
npm test Jest. One file: npm test -- src/features/posts
npm run check typecheck + lint + format:check + test. Same as the CI checks job
npm run doctor expo-doctor: dependency versions and config problems
npm run rename Rename the app identity (see Rename checklist)

Add dependencies with npx expo install <package>, not npm install <package>, so versions match the SDK.

Project structure

.
├── AGENTS.md                     # Agent instructions shared by all coding tools
├── CLAUDE.md                     # Claude Code memory: imports AGENTS.md + Claude-specific notes
├── .claude/                      # Claude Code configuration (see "Claude Code")
├── .github/
│   ├── README.md                 # Repository overview and setup (this file)
│   ├── CONTRIBUTING.md           # Development workflow and review expectations
│   ├── SUPPORT.md                # Troubleshooting and where to ask for help
│   ├── ISSUE_TEMPLATE/           # Bug, improvement, and documentation forms
│   ├── pull_request_template.md  # Change summary and verification
│   ├── dependabot.yml            # GitHub Actions version updates
│   └── workflows/ci.yml          # Quality checks and native configuration validation
├── app.config.ts                 # App identity, icons, splash, plugins, build variants
├── eas.json                      # EAS Build profiles: development, preview, production
├── assets/images/                # Placeholder icon and splash artwork (+ README with sizes)
├── scripts/rename.mjs            # Rename script
├── src/
│   ├── app/                      # Expo Router routes only
│   │   ├── _layout.tsx           # Root: providers, theme, splash, Stack
│   │   ├── +not-found.tsx
│   │   ├── (tabs)/
│   │   │   ├── _layout.tsx       # Tab navigator
│   │   │   ├── index.tsx         # Posts tab
│   │   │   └── settings.tsx      # Settings tab
│   │   └── posts/[id].tsx        # Post detail
│   ├── features/
│   │   ├── posts/                # Example feature: api.ts, queries.ts, screens, components, tests
│   │   └── settings/
│   ├── components/               # Shared UI: AppText, Button, Screen, Loading/Error/Empty states
│   ├── lib/api/                  # request() and ApiError
│   ├── config/env.ts             # Validated EXPO_PUBLIC_* config
│   ├── state/                    # zustand stores
│   ├── theme/                    # Tokens + useAppTheme()
│   ├── providers/                # QueryClient and other app-wide providers
│   └── test/                     # Jest setup and render helpers
├── .env.example
├── .nvmrc
├── eslint.config.js
├── jest.config.js
├── tsconfig.json
└── package.json

Data flow for a screen: route file (src/app) → feature screen → TanStack Query hook (queries.ts) → API function (api.ts) → request() → fetch, with the response validated by a zod schema. Errors surface as ApiError and are shown with toUserMessage().

Adding a feature

  1. Create src/features/<name>/ with api.ts, queries.ts, <Name>Screen.tsx, __tests__/. Copy the shape of src/features/posts/.
  2. Add a route file in src/app/ that renders the screen.
  3. npm run check.

In Claude Code, /new-feature <name> performs these steps.

Native folders

This template uses Expo's Continuous Native Generation: ios/ and android/ are generated from app.config.ts and its plugins by npm run prebuild, npm run ios, npm run android, or EAS Build, and they are gitignored. All native build settings the template needs (bundle IDs, icons, splash, URL scheme, variants) are declared in app.config.ts. CI regenerates both projects on every push to catch config errors.

Reasons: renaming and SDK upgrades become config edits instead of Xcode/Gradle diffs, and the generated projects cannot drift from the config.

If you need to edit native code directly (custom native modules without a config plugin, vendor SDKs that require manual project edits):

  1. Run npm run prebuild.
  2. Remove /ios and /android from .gitignore and commit both folders.
  3. From then on, app.config.ts changes to native settings no longer apply automatically. Make them in Xcode/Gradle, or re-run prebuild and re-apply your edits.
  4. Update AGENTS.md ("Native code and configuration") and .claude/rules/native-config.md.

Configuration and environments

Where What When it is read
app.config.ts APP Name, slug, scheme, bundle ID, Android package, EAS IDs Build time
APP_VARIANT env var development / preview / production Build time
.env (EXPO_PUBLIC_*) Runtime config (API base URL), validated in src/config/env.ts Inlined at bundle time
  • EXPO_PUBLIC_* values are compiled into the JavaScript bundle and are readable by anyone with the app. Do not store secrets in them. Server-side secrets belong on your backend.
  • To add a variable: add it to .env.example, to the zod schema and the object literal in src/config/env.ts (Expo only inlines literal process.env.EXPO_PUBLIC_X accesses), and to src/test/setup.ts if tests need it. For EAS builds, set it as an EAS environment variable.
  • Variants: development and preview append .dev / .preview to the bundle ID and (Dev) / (Preview) to the name so they install alongside production. eas.json sets APP_VARIANT per profile. Reference: docs.expo.dev/build-reference/variants.

Builds and releases

Release builds use EAS (eas.json). One-time setup:

npx eas-cli@latest login
npx eas-cli@latest init          # creates the EAS project; copy projectId and owner into app.config.ts APP

Then:

npx eas-cli@latest build --profile development --platform all   # dev client for testers
npx eas-cli@latest build --profile preview --platform all       # internal distribution
npx eas-cli@latest build --profile production --platform all    # store builds
npx eas-cli@latest submit --platform ios|android                # upload to stores

eas.json uses appVersionSource: remote, so EAS manages build numbers; version in app.config.ts is the user-facing version. Signing credentials are managed by EAS by default and must never be committed (.gitignore excludes keystores, .p8, .p12 and provisioning profiles).

Local release builds without EAS are possible after npm run prebuild with Xcode (Archive) and Gradle (./gradlew bundleRelease); you then manage signing yourself.

CI

.github/workflows/ci.yml runs on pull requests and pushes to main:

  • checks: npm ci, typecheck, lint, format check, Jest, expo-doctor.
  • native-config: expo prebuild and expo export (JS bundle for iOS and Android) for the development and production variants on Ubuntu. This validates app.config.ts, plugins, and Metro bundling. It does not compile the native apps; that needs macOS runners with Xcode (iOS) or the Android SDK. Add EAS builds or platform build jobs when you are ready to pay for them.

Permissions are contents: read. No secrets are used.

Claude Code

Files Claude Code reads in this repository. Conventions were checked against the Claude Code docs on 2026-10-16 (.claude directory, memory, settings, permissions, hooks, skills, subagents).

Path Purpose Committed
AGENTS.md Project instructions in the cross-tool format: commands, layout, conventions, security rules. Edit this file for instructions every agent should follow. yes
CLAUDE.md Loaded at the start of every Claude Code session. Imports AGENTS.md with @AGENTS.md, then adds Claude-specific notes. When both files exist Claude Code reads CLAUDE.md, so the import is required. yes
CLAUDE.local.md Personal project notes. Create it yourself if needed. no
.claude/settings.json Shared settings: permission allow/ask/deny rules, the format hook, and the Expo plugin. yes
.claude/settings.local.json Personal overrides; takes precedence over settings.json. no
.claude/hooks/format-file.mjs PostToolUse hook: runs Prettier on every file Claude edits or writes. Reports syntax errors back to Claude. yes
.claude/rules/*.md Path-scoped instructions (paths: frontmatter) loaded only when Claude works on matching files: routes.md, testing.md, native-config.md. yes
.claude/agents/code-reviewer.md Subagent: read-only review of the current diff against repo conventions. yes
.claude/agents/test-writer.md Subagent: writes or repairs Jest tests. yes
.claude/skills/new-feature/SKILL.md /new-feature <name>: scaffolds a feature folder, route and tests. yes
.claude/skills/verify/SKILL.md /verify: runs all checks and reports PASS/FAIL per check. yes
.claude/skills/rename-app/SKILL.md /rename-app: guided rename using scripts/rename.mjs. Manual-only (disable-model-invocation). yes

Notes:

  • Slash commands are implemented as skills (.claude/skills/<name>/SKILL.md). The docs state that custom commands have been merged into skills; .claude/commands/<name>.md files still work and create the same /name, but skills are recommended for new work. Add a command file there if you prefer the single-file format.
  • Permissions: check commands run without prompts; dependency installs, prebuild, EAS commands and git push always ask; .env files and signing material are unreadable by Claude's file tools. Deny rules on Read do not stop every shell form of reading a file; they are a guard, not a security boundary.
  • enabledPlugins enables expo@claude-plugins-official, the Expo plugin that Expo's own SDK 58 default template enables. Remove the entry if you do not want it.
  • .mcp.json (project MCP servers) is not included. Add one if your team uses MCP servers.
  • The hook needs node on PATH and npm ci already run; it does nothing otherwise.

Rename checklist

Run the script first, then work through the rest. npm run rename -- --check exits non-zero while template identity values remain.

npm run rename -- --name "Acme Notes" --slug acme-notes --bundle-id com.acme.notes --dry-run
npm run rename -- --name "Acme Notes" --slug acme-notes --bundle-id com.acme.notes
# optional: --android-package com.acme.notes_app --scheme acmenotes

The script validates the values (Android packages cannot contain hyphens or Java keywords) and updates app.config.ts, package.json and package-lock.json.

  • Run npm run rename (display name, slug, iOS bundle ID, Android package, URL scheme).
  • Replace artwork in assets/images/ (sizes in assets/images/README.md) and set SPLASH_BACKGROUND / ADAPTIVE_ICON_BACKGROUND in app.config.ts.
  • Replace the palette in src/theme/tokens.ts.
  • Set EXPO_PUBLIC_API_BASE_URL in .env.example, your .env, and EAS environment variables.
  • Replace or delete the example src/features/posts/ feature and its route src/app/posts/, and update the tabs in src/app/(tabs)/_layout.tsx.
  • Run npx eas-cli@latest init and fill easProjectId and owner in app.config.ts.
  • Set version in app.config.ts.
  • Update the title and description at the top of this README.
  • Choose a license and update LICENSE (the template's MIT license has a placeholder holder).
  • Update AGENTS.md with your product's domain terms and any decisions from below.
  • If ios/ or android/ exist locally: npm run prebuild.
  • npm run check and npm run doctor.
  • Mark the GitHub repo as a template only if you are publishing your own template.

Decision points

The template makes no choice for these. Record decisions in AGENTS.md.

Decision Template state Notes
Backend / API JSONPlaceholder demo API for the example feature only Replace EXPO_PUBLIC_API_BASE_URL and the posts schemas.
Authentication None Add auth headers in src/lib/api/client.ts. Store tokens with expo-secure-store, not AsyncStorage.
Client state library zustand (one small persisted store) Replaceable; only src/state/ and useAppTheme depend on it.
Server state library TanStack Query Widely used with React Native; handles caching, retries, cancellation.
Committed native folders No (CNG) See Native folders.
Crash reporting / analytics None Choose a vendor; most provide an Expo config plugin.
Internationalization None; strings are inline English Options: expo-localization plus an i18n library.
Over-the-air updates Not configured expo-updates + EAS Update, if you want JS updates without store review.
End-to-end tests None Maestro or Detox; both need built apps, so they belong in a separate CI job.
Web target Not supported react-dom is installed only because Expo Router declares it as a peer dependency.
Tablet support iOS supportsTablet: true, portrait only Adjust orientation and ios.supportsTablet in app.config.ts.
Package manager npm Switching needs a new lockfile and CI cache change.

Known limitations

The following is the original authoring verification record (2026-10-16, Linux, Node 22.19, npm 10.9), rather than a live CI status. Run the checks in your own environment:

  • Executed and passing: npm run typecheck (also with Expo Router's generated typed routes), npm run lint, npm run format:check, npm test (23 tests), npx expo-doctor (20/20), npx expo config for each variant, npx expo prebuild --no-install generating both ios/ and android/ with the placeholder identifiers, icons and splash, npx expo export producing iOS and Android Hermes bundles, scripts/rename.mjs (dry run, validation errors, and a real run on a copy), and the Prettier hook (sample inputs piped to it, and triggered in a live Claude Code session while the template was written).
  • Not run: compiling or launching the app on iOS or Android (no Xcode or Android SDK in the authoring environment), EAS builds, the GitHub Actions workflow on GitHub, the subagents and skills inside a Claude Code session, and the expo@claude-plugins-official plugin install.
  • Run npm run ios and npm run android once after cloning to confirm your toolchain.

🤝 Contributing and support

Read the contribution guide for the development workflow, testing expectations, and pull request checklist. The support guide covers troubleshooting and choosing the right issue form.

License

Distributed under the MIT License. See LICENSE for details.

About

React Native + Expo template for iOS and Android with strict TypeScript, routing, validated APIs, themes, tests, and CI. Includes Claude Code conventions: shared instructions, scoped rules, skills, review/test agents, and a formatting hook.

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages