“Next.js의 파일 기반 라우팅, 앱에서도 똑같이 됩니다.”
이 글에서는 Expo Router로 여러 화면을 만들고 이동하는 방법을 코드와 함께 살펴봅니다.
Expo Router란?
Expo Router는 파일 구조가 곧 라우팅 구조가 되는 방식입니다.
Next.js의 App Router와 거의 동일한 개념이라 웹 개발자에게 매우 익숙합니다.
app/
├── index.tsx → 홈 화면 (/)
├── profile.tsx → 프로필 화면 (/profile)
├── settings/
│ └── index.tsx → 설정 화면 (/settings)
└── post/
└── [id].tsx → 동적 화면 (/post/123)
파일을 만들면 자동으로 화면(라우트)이 생성됩니다.
기본 화면 이동
Link 컴포넌트 — 선언적 이동
// app/index.tsx
import { View, Text, StyleSheet } from 'react-native';
import { Link } from 'expo-router';
export default function HomeScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>홈 화면</Text>
{/* 기본 이동 */}
<Link href="/profile" style={styles.link}>
프로필로 이동
</Link>
{/* 버튼 스타일로 이동 */}
<Link href="/settings" asChild>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>설정으로 이동</Text>
</TouchableOpacity>
</Link>
</View>
);
}
useRouter — 코드로 이동
// app/index.tsx
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useRouter } from 'expo-router';
export default function HomeScreen() {
const router = useRouter();
const handleLogin = () => {
// 로그인 처리 후 이동
router.push('/profile'); // 스택에 쌓으며 이동
router.replace('/home'); // 현재 화면을 교체 (뒤로가기 불가)
router.back(); // 이전 화면으로 이동
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={handleLogin}>
<Text style={styles.buttonText}>로그인</Text>
</TouchableOpacity>
</View>
);
}
동적 라우팅 — 파라미터 전달
파라미터 넘기기
// app/index.tsx
import { Link } from 'expo-router';
export default function HomeScreen() {
return (
// /post/123 으로 이동
<Link href="/post/123">게시글 보기</Link>
);
}
파라미터 받기
// app/post/[id].tsx
import { View, Text, StyleSheet } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function PostScreen() {
const { id } = useLocalSearchParams();
return (
<View style={styles.container}>
<Text style={styles.title}>게시글 #{id}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
title: { fontSize: 24, fontWeight: 'bold' },
});
쿼리 파라미터 전달
// 이동할 때
router.push({
pathname: '/search',
params: { keyword: '리액트', category: 'dev' },
});
// 받을 때 (app/search.tsx)
const { keyword, category } = useLocalSearchParams();
스택 네비게이션
스택은 화면을 쌓아가며 이동하는 방식입니다.
뒤로가기 버튼으로 이전 화면으로 돌아갈 수 있습니다.
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen
name="index"
options={{
title: '홈',
headerStyle: { backgroundColor: '#007AFF' },
headerTintColor: '#fff',
}}
/>
<Stack.Screen
name="profile"
options={{
title: '프로필',
headerBackTitle: '뒤로',
}}
/>
<Stack.Screen
name="post/[id]"
options={{ title: '게시글' }}
/>
</Stack>
);
}
탭 네비게이션
하단 탭 메뉴를 구성하는 방식입니다.
앱에서 가장 많이 사용되는 네비게이션 패턴입니다.
app/
├── _layout.tsx # 루트 레이아웃
└── (tabs)/
├── _layout.tsx # 탭 레이아웃
├── index.tsx # 홈 탭
├── explore.tsx # 탐색 탭
└── profile.tsx # 프로필 탭
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: '#999',
tabBarStyle: {
backgroundColor: '#fff',
borderTopColor: '#eee',
},
}}
>
<Tabs.Screen
name="index"
options={{
title: '홈',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" color={color} size={size} />
),
}}
/>
<Tabs.Screen
name="explore"
options={{
title: '탐색',
tabBarIcon: ({ color, size }) => (
<Ionicons name="search" color={color} size={size} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: '프로필',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" color={color} size={size} />
),
}}
/>
</Tabs>
);
}
모달 화면
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{
presentation: 'modal', // 모달로 표시
title: '모달 화면',
}}
/>
</Stack>
);
}
// app/modal.tsx
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useRouter } from 'expo-router';
export default function ModalScreen() {
const router = useRouter();
return (
<View style={styles.container}>
<Text style={styles.title}>모달 화면입니다</Text>
<TouchableOpacity style={styles.button} onPress={() => router.back()}>
<Text style={styles.buttonText}>닫기</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
title: { fontSize: 20, marginBottom: 20 },
button: {
backgroundColor: '#007AFF',
padding: 12,
borderRadius: 8,
},
buttonText: { color: '#fff', fontWeight: '600' },
});
헤더 커스터마이징
// app/profile.tsx
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { Stack, useRouter } from 'expo-router';
export default function ProfileScreen() {
const router = useRouter();
return (
<View style={styles.container}>
{/* 이 화면의 헤더만 커스터마이징 */}
<Stack.Screen
options={{
title: '내 프로필',
headerRight: () => (
<TouchableOpacity onPress={() => router.push('/settings')}>
<Text style={styles.headerButton}>설정</Text>
</TouchableOpacity>
),
}}
/>
<Text>프로필 화면</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
headerButton: { color: '#007AFF', fontSize: 16 },
});
실전 예제 — 탭 + 스택 조합 구조
대부분의 앱이 사용하는 구조입니다.
app/
├── _layout.tsx # 루트 (Stack)
├── (tabs)/
│ ├── _layout.tsx # 하단 탭
│ ├── index.tsx # 홈 탭
│ ├── search.tsx # 검색 탭
│ └── profile.tsx # 프로필 탭
├── post/
│ └── [id].tsx # 게시글 상세 (탭 위에 스택으로 쌓임)
└── modal.tsx # 모달
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
{/* 탭 전체를 하나의 스크린으로 */}
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="post/[id]" options={{ title: '게시글' }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack>
);
}
Next.js vs Expo Router 비교
| 항목 | Next.js (App Router) | Expo Router |
|---|---|---|
| 라우팅 방식 | 파일 기반 | 파일 기반 ✅ 동일 |
| 동적 라우팅 | [id].tsx | [id].tsx ✅ 동일 |
| 레이아웃 | _layout.tsx | _layout.tsx ✅ 동일 |
| 이동 방법 | useRouter, <Link> | useRouter, <Link> ✅ 동일 |
| 네비게이션 타입 | 웹 히스토리 | 스택 / 탭 / 모달 |
Next.js 경험이 있다면 Expo Router는 매우 빠르게 익힐 수 있습니다.
정리
| 상황 | 방법 |
|---|---|
| 선언적 이동 | <Link href="/path"> |
| 코드로 이동 | router.push('/path') |
| 파라미터 전달 | href="/post/123" 또는 params |
| 파라미터 수신 | useLocalSearchParams() |
| 스택 구성 | <Stack> + <Stack.Screen> |
| 탭 구성 | <Tabs> + <Tabs.Screen> |
| 모달 | presentation: 'modal' |
다음 편에서는 상태 관리와 API 연동으로 실제 데이터를 다루는 방법을 살펴보겠습니다.
시리즈 목차
- 1편 Expo 개발 환경 세팅 & 첫 앱 실행
- 2편 화면 구성 & 핵심 컴포넌트
- 3편 화면 이동 구현 (Expo Router) ← 현재 글
- 4편 상태 관리 & API 연동
- 5편 디바이스 기능 활용 (카메라, 위치, 알림)
- 6편 Firebase 연동
- 7편 빌드 & 앱스토어 배포