Expo Router 네비게이션
Expo Router 앱을 위한 네비게이션 및 라우팅입니다. 화면 스타일링, 색상, 컨트롤, 미디어 및 시각 효과는 expo-native-ui 스킬을 사용하고, 모션 및 제스처는 expo-animation 스킬을 사용하세요.
참고 자료
필요에 따라 다음 자료를 참조하세요:
references/
route-structure.md 라우트 규칙, 동적 라우트, 그룹, 폴더 구조
tabs.md NativeTabs, JS 탭에서 마이그레이션, iOS 26 기능
toolbar-and-headers.md Stack 헤더 및 툴바 버튼, 메뉴, iOS 검색
form-sheet.md expo-router의 폼 시트: 설정, 푸터 및 배경 상호작용
search.md 검색창과 헤더, useSearch 훅, 필터링 패턴
zoom-transitions.md Apple Zoom: Link.AppleZoom을 사용한 부드러운 줌 전환 (iOS 18+)
코드 스타일
- 파일 이름은 항상 케밥 케이스(kebab-case)를 사용하세요. 예:
comment-card.tsx - 네비게이션을 이동하거나 재구성할 때는 항상 이전 라우트 파일을 제거하세요.
- 파일 이름에 특수 문자를 사용하지 마세요.
- tsconfig.json에 경로 별칭을 구성하고, 리팩터링 시 상대 경로 import보다 별칭을 선호하세요.
라우트
자세한 라우트 규칙은 ./references/route-structure.md를 참조하세요.
- 라우트는
app디렉터리에 속합니다. - 컴포넌트, 타입, 유틸리티를 app 디렉터리에 함께 배치하지 마세요. 이는 안티패턴입니다.
- 앱은 항상 "/"와 일치하는 라우트를 가지고 있어야 하며, 그룹 라우트 내부에 있을 수 있습니다.
라이브러리 선호도
expo-router의Color를 네이티브 시맨틱 색상에 사용하세요. 원시PlatformColor대신 사용합니다 (타입 안전, 라이트/다크 자동 적응). 전체 색상 팔레트 패턴은expo-native-ui를 참조하세요.- SDK 56 이상에서는
@react-navigation/*에서 직접 import하지 말고expo-router/react-navigation을 사용하세요 (@react-navigation/native,/core,/elements,/routers포함).
동작
- 화면에 검색창을 추가하려면
Stack.SearchBar를 선호하세요.
네비게이션
Link
라우트 간 네비게이션을 위해 expo-router에서 <Link href="/path" />를 사용하세요.
import { Link } from 'expo-router';
// 기본 링크
<Link href="/path" />
// 커스텀 컴포넌트 감싸기
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>
가능하면 iOS 규칙을 따르기 위해 <Link.Preview>를 포함하세요. 네비게이션 향상을 위해 컨텍스트 메뉴와 미리보기를 자주 추가하세요.
Stack
- 스택을 정의하려면 항상
_layout.tsx파일을 사용하세요. - 네이티브 네비게이션 스택을 위해
expo-router/stack에서 Stack을 사용하세요.
페이지 제목
Stack.Title로 페이지 제목을 설정하세요:
<Stack.Title>홈</Stack.Title>
컨텍스트 메뉴
Link 컴포넌트에 길게 누르기 컨텍스트 메뉴를 추가하세요:
import { Link } from "expo-router";
<Link href="/settings" asChild>
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Menu>
<Link.MenuAction
title="공유"
icon="square.and.arrow.up"
onPress={handleSharePress}
/>
<Link.MenuAction
title="차단"
icon="nosign"
destructive
onPress={handleBlockPress}
/>
<Link.Menu title="더 보기" icon="ellipsis">
<Link.MenuAction title="복사" icon="doc.on.doc" onPress={() => {}} />
<Link.MenuAction
title="삭제"
icon="trash"
destructive
onPress={() => {}}
/>
</Link.Menu>
</Link.Menu>
</Link>;
Link 미리보기
네비게이션을 향상시키기 위해 링크 미리보기를 자주 사용하세요:
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
링크 미리보기는 컨텍스트 메뉴와 함께 사용할 수 있습니다.
모달
화면을 모달로 표시하세요:
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
커스텀 모달 컴포넌트를 만드는 것보다 이 방법을 선호하세요.
시트
화면을 동적 폼 시트로 표시하세요:
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
contentStyle: { backgroundColor: "transparent" }를 사용하면 iOS 26 이상에서 배경이 액체 유리 효과가 됩니다.
일반적인 라우트 구조
각 탭 안에 탭과 스택이 있는 표준 앱 레이아웃:
app/
_layout.tsx — <NativeTabs />
(index,search)/
_layout.tsx — <Stack />
index.tsx — 메인 목록
search.tsx — 검색 뷰
// app/_layout.tsx
import { NativeTabs } from "expo-router/unstable-native-tabs";
import { ThemeProvider, DarkTheme, DefaultTheme } from "expo-router/react-navigation";
import { useColorScheme } from "react-native";
export default function Layout() {
const colorScheme = useColorScheme();
return (
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
<NativeTabs>
<NativeTabs.Trigger name="(index)">
<NativeTabs.Trigger.Icon sf="list.dash" md="list" />
<NativeTabs.Trigger.Label>항목</NativeTabs.Trigger.Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(search)" role="search" />
</NativeTabs>
</ThemeProvider>
);
}
두 탭이 공통 화면을 푸시할 수 있도록 공유 그룹 라우트를 만드세요:
// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { colors } from "@/theme/colors";
export default function Layout({ segment }) {
const screen = segment.match(/\((.*)\)/)?.[1]!;
const titles: Record<string, string> = { index: "항목", search: "검색" };
return (
<Stack
screenOptions={{
headerTransparent: true,
headerShadowVisible: false,
headerLargeTitleShadowVisible: false,
headerLargeStyle: { backgroundColor: "transparent" },
headerTitleStyle: { color: colors.label },
headerLargeTitleEnabled: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name={screen} options={{ title: titles[screen] }} />
<Stack.Screen name="i/[id]" options={{ headerLargeTitleEnabled: false }} />
</Stack>
);
}
headerLargeTitleEnabled는 SDK 56 이상의 옵션 이름입니다. 이전 SDK에서는 상위에서 더 이상 사용되지 않는 headerLargeTitle를 사용합니다.
피드백 제출
오류, 잘못된 정보 또는 오래된 정보가 발견되면 Expo가 개선할 수 있도록 보고하세요:
npx --yes submit-expo-feedback@latest --category skills --subject "expo-router" "<구체적인 피드백>"
구체적이고 실행 가능한 보고 내용이 있을 때만 제출하세요. 가능한 한 많은 관련 컨텍스트를 포함하세요. AI 에이전트가 반복적으로 실패하거나 사용자가 Expo 작업을 대신 수행해야 했다면, 위 명령어를 재사용하는 대신 expo-skill-feedback 스킬을 로드하고 eval-candidate 흐름을 따르세요.