본문으로 건너뛰기
cccskills
무료GitHub에 공개

mobile-navigation-expo-router

File-based routing and navigation for Expo/React Native

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(7)

  • SKILL.md16.8 KB
  • examples/api-routes.md4.0 KB
  • examples/auth.md6.1 KB
  • examples/core.md13.3 KB
  • examples/web.md5.1 KB
  • metadata.yaml580 B
  • reference.md6.3 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Expo Router Patterns

Quick Guide: File-based routing for React Native and web. Files in app/ become routes automatically. Use _layout.tsx for navigation structure (Stack, Tabs), groups (name)/ for URL-invisible organization, [param] for dynamic segments. SDK 53+: use Stack.Protected with a guard prop for authentication. Enable typedRoutes for compile-time route safety. API routes use +api.ts suffix.


<critical_requirements>

CRITICAL: Before Using This Skill

(You MUST define navigation structure in _layout.tsx files -- screens without a layout parent default to a basic Stack)

(You MUST use Stack.Protected with guard prop for authentication flows in SDK 53+ -- NOT imperative redirects in useEffect)

(You MUST use useLocalSearchParams for route params in screens -- useGlobalSearchParams causes unnecessary re-renders on unfocused screens)

(You MUST enable typedRoutes in app.json experiments for compile-time route validation -- catches invalid navigation at build time)

</critical_requirements>


Auto-detection: expo-router, Expo Router, file-based routing, _layout.tsx, Stack.Screen, Tabs.Screen, useRouter, useLocalSearchParams, useSegments, usePathname, Link href, router.push, router.replace, router.dismiss, router.dismissTo, +api.ts, +not-found, Stack.Protected, generateStaticParams, expo-router/head, Slot, Redirect, useFocusEffect, NativeTabs, headless tabs, TabSlot, TabTrigger

When to use:

  • Setting up file-based navigation in an Expo app
  • Implementing authentication flows with route protection
  • Creating tab, stack, or modal navigation layouts
  • Building API routes for server-side logic
  • Configuring typed routes for compile-time safety
  • Adding deep linking and static rendering for web

Key patterns covered:

  • File convention: _layout.tsx, [param], [...slug], (group)/, +api.ts, +not-found.tsx
  • Layout navigators: Stack, Tabs, headless tabs, native tabs
  • Authentication: Stack.Protected guard pattern (SDK 53+), redirect pattern (SDK 52)
  • Navigation hooks: useRouter, useLocalSearchParams, useSegments, usePathname
  • API routes with standard Request/Response
  • Typed routes with auto-generated TypeScript definitions
  • Modal routes, shared routes between tabs, nested navigation

When NOT to use:

  • Apps that need fully custom native navigation controllers beyond what React Navigation provides
  • Simple single-screen apps with no navigation
  • Web-only projects where a web-native router is more appropriate

<philosophy>

Philosophy

Expo Router maps the filesystem to your navigation hierarchy. Every file in app/ is a route; every _layout.tsx defines how its sibling routes are presented (stack, tabs, drawer). This convention-over-configuration approach means:

  1. URLs are first-class -- every screen has a URL, enabling deep linking on mobile and SEO on web without extra configuration
  2. Layouts are composable -- nest _layout.tsx files to create any navigation structure (tabs containing stacks containing modals)
  3. The file tree IS the sitemap -- new developers understand navigation by reading the directory structure, not a central config
  4. Universal by default -- the same route definitions work on iOS, Android, and web

Mental model: Think of app/ as a website. _layout.tsx files are the "chrome" (nav bars, tab bars). Route files are the "pages." Groups (name)/ organize without affecting URLs. This maps directly to how web routing works, which is intentional -- Expo Router is built on top of React Navigation but presents a web-like API.

</philosophy>
<patterns>

Core Patterns

Pattern 1: File Conventions

Every file in app/ maps to a route. Special characters change behavior:

FileURLPurpose
index.tsx/ (or parent path)Default route for directory
about.tsx/aboutStatic route
[id].tsx/:idDynamic segment
[...slug].tsx/a/b/cCatch-all segments
_layout.tsxN/AWraps sibling routes in navigator
(group)/Not in URLOrganizes routes without URL impact
+not-found.tsxN/A404 fallback for unmatched routes
+api.tsServer endpointAPI route handler
+html.tsxN/ARoot HTML wrapper (web static rendering)

Key insight: Groups (name)/ are purely organizational. (tabs)/home.tsx and home.tsx both resolve to /home. Use groups to apply different layouts to different route sets without changing URLs.

Full directory structure examples: examples/core.md


Pattern 2: Layout Routes

_layout.tsx files wrap their sibling routes in a navigator. The layout determines HOW routes are presented (stack push, tab switch, modal overlay).

// app/_layout.tsx -- Root layout wrapping entire app
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" }} />
      <Stack.Screen name="+not-found" />
    </Stack>
  );
}

Why this matters: Without a _layout.tsx, routes get a default Stack navigator with default headers. Always define layouts explicitly for control over headers, transitions, and navigation structure.

Gotcha: The name prop in Stack.Screen/Tabs.Screen must match the filename (without extension) or directory name exactly. name="(tabs)" matches the (tabs)/ directory.

Full layout examples (tabs, nested stacks, drawers): examples/core.md


Pattern 3: Navigation Hooks

import {
  useRouter,
  useLocalSearchParams,
  usePathname,
  useSegments,
} from "expo-router";

// useRouter -- imperative navigation
const router = useRouter();
router.push("/users/123"); // Add to stack
router.replace("/home"); // Replace current (no back)
router.back(); // Go back
router.dismiss(); // Pop one screen in nearest stack
router.dismissTo("/home"); // Pop until reaching /home
router.dismissAll(); // Pop to first screen in stack
router.canGoBack(); // Check if back is possible
router.canDismiss(); // Check if dismiss is possible
router.prefetch("/heavy-screen"); // Preload in background

// useLocalSearchParams -- route params for focused screen only
const { id } = useLocalSearchParams<{ id: string }>();

// usePathname -- current path without query params
const pathname = usePathname(); // "/users/123"

// useSegments -- raw file segments of current route
const segments = useSegments(); // ["users", "[id]"]

Critical: Use useLocalSearchParams over useGlobalSearchParams. The global variant re-renders the component whenever ANY route's params change -- even when the screen is unfocused in the background. Local only updates when the screen is focused.

Full hook usage examples: examples/core.md


Pattern 4: Authentication with Stack.Protected (SDK 53+)

The recommended pattern uses Stack.Protected with a guard prop to declaratively show/hide routes based on auth state.

// app/_layout.tsx
import { Stack } from "expo-router";
import { useSession } from "../ctx";

function RootNavigator() {
  const { session } = useSession();

  return (
    <Stack>
      <Stack.Protected guard={!!session}>
        <Stack.Screen name="(app)" />
      </Stack.Protected>

      <Stack.Protected guard={!session}>
        <Stack.Screen name="sign-in" />
      </Stack.Protected>
    </Stack>
  );
}

How guard works: When guard is false, the screens inside are inaccessible. If a user tries to navigate to a protected screen, or a screen becomes protected while active, they are redirected to the first available unprotected screen.

Gotcha: All routes remain defined and accessible in the file system. Stack.Protected controls runtime accessibility, not build-time elimination. Deep links to protected routes trigger redirects to the sign-in screen.

Full auth pattern with SessionProvider and splash screen: examples/auth.md Legacy redirect pattern (SDK 52): examples/auth.md


Pattern 5: Modal Routes

Modals are defined as regular route files but configured with presentation: "modal" in the parent layout.

// app/_layout.tsx
<Stack>
  <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
  <Stack.Screen
    name="modal"
    options={{
      presentation: "modal",
      headerShown: true,
      title: "Settings",
    }}
  />
  <Stack.Screen
    name="sheet"
    options={{
      presentation: "formSheet",
      sheetGrabberVisible: true,
      sheetCornerRadius: 16,
    }}
  />
</Stack>

Key insight: Modals sit outside tab groups so they overlay the entire app. Navigation to a modal from any tab: router.push("/modal"). Dismiss with router.back() or router.dismiss().

Full modal examples: examples/core.md


Pattern 6: API Routes

Files with +api.ts suffix define server-side endpoints. They use standard Web Request/Response APIs.

// app/api/users+api.ts
export async function GET(request: Request) {
  const users = await db.users.findMany();
  return Response.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  const user = await db.users.create(body);
  return Response.json(user, { status: 201 });
}

Requires web.output: "server" in app.json. For native apps, set origin in the expo-router plugin config to point to your deployed server.

Limitation: API routes bundle to CommonJS, no dynamic imports, no platform-specific extensions (+api.web.ts does not work).

Full API route examples with error handling: examples/api-routes.md


Pattern 7: Typed Routes

Enable compile-time route validation by setting experiments.typedRoutes: true in app.json. The dev server auto-generates type definitions.

// With typedRoutes enabled:
router.push("/about"); // OK
router.push("/nonexistent"); // TypeScript error
router.push({
  pathname: "/users/[id]",
  params: { id: "123" }, // Typed params required
});

// Typed search params
const { id } = useLocalSearchParams<"/users/[id]">();
// id is typed as string

Gotcha: Generated types are git-ignored. CI pipelines need npx expo customize tsconfig.json to regenerate types before type-checking. Relative paths are not supported -- always use absolute paths.

Typed routes setup and examples: examples/core.md


Pattern 8: Static Rendering and Head Metadata (Web)

Static rendering generates HTML at build time for SEO and fast initial loads.

// app.json: { "web": { "output": "static" } }

// app/about.tsx
import Head from "expo-router/head";
import { Text } from "react-native";

export default function AboutPage() {
  return (
    <>
      <Head>
        <title>About Us</title>
        <meta name="description" content="Learn about our company" />
      </Head>
      <Text>About page content</Text>
    </>
  );
}

For dynamic routes, export generateStaticParams to pre-render pages at build time:

export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map((post) => ({ id: post.id }));
}

Full static rendering and Head examples: examples/web.md

</patterns>

Detailed Resources:


<decision_framework>

Decision Frameworks

Expo Router provides multiple navigation patterns. The key decisions:

  1. Route type -- static, dynamic, catch-all, grouped, API? See reference.md for the full route type decision tree.
  2. Navigation method -- declarative <Link> vs imperative router.push/replace/dismiss? See reference.md for the navigation method decision tree.
  3. Layout navigator -- Stack, Tabs, NativeTabs, headless tabs, or <Slot />? See reference.md for the layout navigator selection guide.
  4. Hook choice -- useLocalSearchParams vs useGlobalSearchParams, useRouter vs <Link>, useFocusEffect vs useEffect? See reference.md for the hook selection table.

Quick rules:

  • Prefer <Link> for static navigation in UI, router.push for programmatic navigation in event handlers
  • Always use useLocalSearchParams unless you specifically need background screen updates
  • Use useFocusEffect instead of useEffect when data should refresh on screen focus

</decision_framework>


<red_flags>

RED FLAGS

High Priority Issues:

  • Using useGlobalSearchParams when useLocalSearchParams works -- global causes re-renders on ALL route changes, even when screen is in background; use local for screen-specific params
  • Imperative redirects in useEffect for auth (SDK 53+) -- use Stack.Protected with guard prop instead; it's declarative, handles edge cases, and integrates with deep linking correctly
  • Missing _layout.tsx in route groups -- without a layout, the default Stack has default headers and no control over transitions; always define layouts explicitly
  • Storing secrets in API route responses without authentication -- API routes are public endpoints; validate authentication tokens before returning sensitive data

Medium Priority Issues:

  • name prop mismatch in layout screens -- Stack.Screen name="tabs" does not match directory (tabs)/; must be name="(tabs)" exactly
  • Not using presentation: "modal" in parent layout -- configuring modal in the modal file's own layout does nothing; modals must be configured in the parent navigator
  • Calling router.replace in initial render -- causes navigation before the navigator is ready; use Redirect component or useFocusEffect instead

Gotchas & Edge Cases:

  • Deep links to protected routes: Stack.Protected redirects to the first unprotected screen -- deep link target is lost unless you store and replay it after auth
  • Catch-all [...slug] params: Always an array, but useLocalSearchParams may return a string if only one segment; always normalize with Array.isArray(slug) ? slug : [slug]
  • Tab groups reset on tab switch: By default, switching tabs resets the tab's stack; use backBehavior: "history" in Tabs layout to preserve stack per tab
  • Android 5-tab limit: Material Design constrains bottom tabs to 5; native tabs enforce this
  • +not-found.tsx only catches at its directory level -- a +not-found.tsx in app/ won't catch 404s inside app/docs/; each directory needs its own if required
  • Static rendering generateStaticParams runs in Node.js -- no access to React Native APIs, browser APIs, or native modules
  • API route limitation: No dynamic imports, no platform-specific extensions (+api.web.ts is invalid), bundles to CommonJS
  • Typed routes are git-ignored -- CI pipelines fail type checks unless types are regenerated with npx expo customize tsconfig.json
  • Route files require export default -- Expo Router discovers screens via default exports; this overrides project "named exports only" conventions for files in app/

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

(You MUST define navigation structure in _layout.tsx files -- screens without a layout parent default to a basic Stack)

(You MUST use Stack.Protected with guard prop for authentication flows in SDK 53+ -- NOT imperative redirects in useEffect)

(You MUST use useLocalSearchParams for route params in screens -- useGlobalSearchParams causes unnecessary re-renders on unfocused screens)

(You MUST enable typedRoutes in app.json experiments for compile-time route validation -- catches invalid navigation at build time)

Failure to follow these rules will cause navigation bugs, auth bypasses, unnecessary re-renders, and runtime routing errors that typed routes would catch at compile time.

</critical_reminders>

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

agents-inc/skills252026년 10월 17일 업데이트

LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

agents-inc/skills252026년 10월 17일 업데이트

Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

agents-inc/skills252026년 10월 17일 업데이트

Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

agents-inc/skills252026년 10월 17일 업데이트

Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

agents-inc/skills252026년 10월 17일 업데이트

Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

agents-inc/skills252026년 10월 17일 업데이트

agents-inc의 스킬 모두 보기

이 스킬의 문제 신고