Mobile with Expo
Expo is a framework built on top of React Native that provides the tooling and services needed to build, deploy, and iterate on mobile apps. It is to React Native what Next.js is to React — it handles the hard parts (bundling, native module linking, OTA updates) so you can focus on building features.
React Native Basics
React Native uses native UI primitives instead of HTML elements. The core mapping:
| Web | React Native |
|---|---|
<div> | <View> |
<p>, <span> | <Text> |
<img> | <Image> |
<input> | <TextInput> |
<button> | <Pressable> |
<ul> / <li> | <FlatList> |
<a> | <Link> (Expo Router) |
All text must be wrapped in a <Text> component — you cannot place raw strings inside a <View>.
import { View, Text, Pressable } from 'react-native'
export default function Welcome() {
return (
<View>
<Text>Hello from React Native!</Text>
<Pressable onPress={() => alert('Pressed!')}>
<Text>Tap me</Text>
</Pressable>
</View>
)
}
Expo Router
Expo Router provides file-based routing for React Native, similar to the Next.js App Router. Files in the app/ directory become routes:
app/
├── _layout.tsx # Root layout (navigation structure)
├── index.tsx # Home screen (/)
├── login.tsx # Login screen (/login)
└── (tabs)/
├── _layout.tsx # Tab layout
├── feed.tsx # Feed tab (/feed)
└── profile.tsx # Profile tab (/profile)
Layouts, dynamic routes ([id].tsx), and groups ((tabs)) work the same way as in Next.js. If you understood the App Router, Expo Router will feel familiar.
Development Workflow
Expo provides several ways to run your app during development:
- Expo Go — a pre-built app on your phone. Scan a QR code and your app loads instantly. Great for quick iteration, but limited to APIs included in the Expo SDK.
- Development builds — a custom build of your app installed on a simulator or device. Supports any native module. Use this when you need libraries not available in Expo Go.
- Simulators — Xcode's iOS Simulator and Android Studio's Emulator. Run on your machine without a physical device.
Adding the Mobile App to the Monorepo
Scaffold a new Expo app inside the Chirp monorepo:
cd apps
bunx create-expo-app@latest mobile
Expo automatically detects monorepo setups — no manual Metro bundler configuration is needed.
Add the shared packages to the mobile app's package.json:
{
"dependencies": {
"@chirp/trpc": "workspace:*",
"@chirp/auth": "workspace:*"
}
}
Run bun install at the root, then verify the mobile app starts with turbo run dev --filter=@chirp/mobile.
NativeWind
NativeWind brings Tailwind CSS to React Native. Instead of using StyleSheet.create(), you write Tailwind classes just like you do on the web.
Before (plain React Native):
import { View, Text, StyleSheet } from 'react-native'
export default function Card() {
return (
<View style={styles.card}>
<Text style={styles.title}>Hello</Text>
</View>
)
}
const styles = StyleSheet.create({
card: { padding: 16, backgroundColor: '#fff', borderRadius: 8 },
title: { fontSize: 18, fontWeight: 'bold' },
})
After (NativeWind):
import { View, Text } from 'react-native'
export default function Card() {
return (
<View className="p-4 bg-white rounded-lg">
<Text className="text-lg font-bold">Hello</Text>
</View>
)
}
Same result, but with the Tailwind workflow you already know from the web.