Expo Router สำหรับ React Native: คู่มือระบบ Navigation แบบ File-Based ฉบับสมบูรณ์
คู่มือฉบับสมบูรณ์สำหรับ Expo Router ใน React Native ครอบคลุมระบบ routing แบบ file-based, layout, dynamic routes, typed navigation, และรูปแบบขั้นสูงอย่าง modal และ tab

Expo Router นำระบบ routing แบบ file-based มาสู่ React Native แทนที่การตั้งค่า navigation แบบ manual ด้วยแนวทางที่อิงตามข้อตกลง (convention-driven) ซึ่งได้รับแรงบันดาลใจจาก Next.js ตั้งแต่ Expo SDK 57 และ Expo Router การสร้าง navigation ข้ามแพลตฟอร์มสำหรับ Android, iOS และเว็บเพียงแค่สร้างไฟล์ในไดเรกทอรีที่ถูกต้อง
โปรเจกต์ Expo ใหม่ทุกโปรเจกต์มี Expo Router ติดตั้งมาให้พร้อมใช้งาน คำสั่ง npx create-expo-app@latest --template default จะสร้างโปรเจกต์ที่ตั้งค่า file-based routing ไว้เรียบร้อยแล้ว สำหรับโปรเจกต์ที่มีอยู่เดิม เพียงติดตั้งแพ็กเกจ expo-router และอัปเดต entry point ของแอปพลิเคชัน
หลักการทำงานของ File-Based Routing ใน Expo Router
ไฟล์ทุกไฟล์ที่สร้างขึ้นภายในไดเรกทอรี app จะกลายเป็น route โดยอัตโนมัติ เส้นทางของไฟล์จะสอดคล้องกับเส้นทาง URL โดยตรง ทำให้ไม่จำเป็นต้องมีไฟล์ configuration แบบรวมศูนย์อีกต่อไป ตัวอย่างเช่น ไฟล์ app/settings.tsx จะสร้าง route /settings ในขณะที่ app/profile/edit.tsx จะตรงกับ /profile/edit
แนวทางนี้มีข้อได้เปรียบหลักสามประการเมื่อเทียบกับการตั้งค่า React Navigation แบบดั้งเดิม:
- ไม่ต้องตั้งค่าใดๆ: route ถูกสร้างขึ้นทันทีที่สร้างไฟล์
- Deep linking อัตโนมัติ: ทุกหน้าจอมี URL เป็นของตัวเอง ทำให้การแชร์ลิงก์และการทดสอบสะดวกยิ่งขึ้น
- Navigation ที่มี type safety: TypeScript รู้จัก route ทั้งหมดที่มีอยู่ตั้งแต่ขั้นตอน compilation
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}>Welcome</Text>
{/* Link maps directly to file path */}
<Link href="/settings" style={styles.link}>
Open Settings
</Link>
<Link href="/profile/edit" style={styles.link}>
Edit Profile
</Link>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 24 },
title: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 },
link: { fontSize: 16, color: '#61DAFB', marginTop: 12 },
})คอมโพเนนต์ Link จัดการ navigation บนทุกแพลตฟอร์ม บนเว็บจะสร้าง anchor tag พร้อม attribute href ที่เหมาะสมสำหรับ SEO ส่วนบนแพลตฟอร์ม native จะทำงานเป็น stack navigation ตามปกติ
โครงสร้างโปรเจกต์และไฟล์ Layout
Expo Router ใช้ไฟล์ _layout.tsx สำหรับกำหนด navigation container แต่ละไดเรกทอรีสามารถมี layout เป็นของตัวเอง ทำให้เกิดโครงสร้าง navigation แบบซ้อนกันได้ Layout หลักที่อยู่ระดับ root จะครอบคลุมทั้งแอปพลิเคชัน ส่วน layout ที่ซ้อนอยู่ภายในจะควบคุมเฉพาะส่วนที่เกี่ยวข้อง
โครงสร้างโปรเจกต์ตัวอย่างมีลักษณะดังนี้:
app/
_layout.tsx # Root layout (Stack or custom)
index.tsx # Home screen (/)
(tabs)/ # Tab group (parentheses = route group)
_layout.tsx # Tab navigator
home.tsx # /home tab
search.tsx # /search tab
profile.tsx # /profile tab
settings/
_layout.tsx # Settings stack layout
index.tsx # /settings
notifications.tsx # /settings/notifications
privacy.tsx # /settings/privacyRoute groups คือไดเรกทอรีที่ล้อมรอบด้วยวงเล็บ ซึ่งทำหน้าที่จัดระเบียบไฟล์โดยไม่ส่งผลกระทบต่อ URL ไดเรกทอรี (tabs) ในตัวอย่างด้านบนสร้าง tab navigator ขึ้นมา แต่ URL ยังคงเป็น /home, /search และ /profile ไม่ใช่ /tabs/home
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<Stack
screenOptions={{
headerStyle: { backgroundColor: '#1a1a2e' },
headerTintColor: '#ffffff',
headerTitleStyle: { fontWeight: '600' },
}}
>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="settings" options={{ title: 'Settings' }} />
</Stack>
)
}Layout หลักยังทำหน้าที่เป็นจุดเริ่มต้นสำหรับการโหลดฟอนต์ การตั้งค่า provider ต่างๆ และการกำหนดค่าระดับ global อีกด้วย
การสร้าง Tab Navigation ด้วย Expo Router
Tab navigation ต้องมีไฟล์ _layout.tsx อยู่ภายใน route group Expo Router มีคอมโพเนนต์ NativeTabs สำหรับประสบการณ์ native เฉพาะแพลตฟอร์ม และคอมโพเนนต์ Tabs มาตรฐานสำหรับกรณีการใช้งานส่วนใหญ่ ตั้งแต่ SDK 56 Native Tabs รองรับ property disabled เพื่อป้องกันการเลือก tab ในขณะที่ยังคงแสดงผลอยู่
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#61DAFB',
tabBarInactiveTintColor: '#888',
tabBarStyle: {
backgroundColor: '#1a1a2e',
borderTopColor: '#2d2d44',
},
}}
>
<Tabs.Screen
name="home"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="search"
options={{
title: 'Search',
tabBarIcon: ({ color, size }) => (
<Ionicons name="search" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>
)
}ไฟล์หน้าจอของแต่ละ tab จะ export คอมโพเนนต์ React มาตรฐาน ไอคอน, ข้อความ และ badge ของ tab bar สามารถกำหนดค่าได้ผ่าน prop options ใน layout
Dynamic Routes และ Route Parameters
Segment แบบ dynamic ใช้วงเล็บเหลี่ยมในชื่อไฟล์ ไฟล์ที่ตั้งชื่อว่า [id].tsx จะจับคู่กับ segment เดียว ในขณะที่ [...slug].tsx จะจับคู่กับ segment ที่เหลือทั้งหมดในเส้นทาง
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams, Stack } from 'expo-router'
export default function ProductScreen() {
// Extract the dynamic parameter from the URL
const { id } = useLocalSearchParams<{ id: string }>()
return (
<View style={styles.container}>
<Stack.Screen options={{ title: `Product ${id}` }} />
<Text style={styles.heading}>Product Details</Text>
<Text style={styles.id}>ID: {id}</Text>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
id: { fontSize: 16, color: '#888' },
})เมื่อเข้าถึง /product/42 หน้าจอนี้จะแสดงขึ้นพร้อมกับตัวแปร id ที่มีค่าเป็น "42" hook useLocalSearchParams ให้การเข้าถึง route parameters ทั้งหมดพร้อม type safety
สำหรับ catch-all routes นั้น [...slug].tsx จะจับคู่กับ path segments ทั้งหมด:
import { useLocalSearchParams } from 'expo-router'
export default function DocsScreen() {
// /docs/getting-started/installation → slug = ['getting-started', 'installation']
const { slug } = useLocalSearchParams<{ slug: string[] }>()
return <DocViewer path={slug.join('/')} />
}พร้อมที่จะพิชิตการสัมภาษณ์ React Native แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
Typed Routes สำหรับความปลอดภัยในขั้นตอน Compilation
Expo Router สร้าง route types ขึ้นโดยอัตโนมัติเมื่อเปิดใช้ฟีเจอร์ typed routes การตรวจสอบประเภทนี้ช่วยตรวจจับลิงก์ที่เสียตั้งแต่ขั้นตอน compilation แทนที่จะปล่อยให้เกิดข้อผิดพลาดตอน runtime
เปิดใช้งานในไฟล์ app.json:
{
"expo": {
"experiments": {
"typedRoutes": true
}
}
}เมื่อเปิดใช้แล้ว prop href ของคอมโพเนนต์ Link และ argument ของ router.push() จะรับเฉพาะ string ที่ตรงกับ route ที่มีอยู่จริงเท่านั้น:
import { router } from 'expo-router'
function handleCheckout(cartId: string) {
// TypeScript validates this route exists
router.push(`/product/${cartId}`)
// This would cause a compile error if /nonexistent doesn't exist
// router.push('/nonexistent')
}Typed routes ทำงานได้ดีเป็นพิเศษเมื่อใช้ร่วมกับ useLocalSearchParams เนื่องจาก types ที่สร้างขึ้นจะรับประกันว่าชื่อ parameter ในการกำหนด route ตรงกับชื่อที่ใช้ในคอมโพเนนต์ที่เรียกใช้ ช่วยป้องกัน bug ที่ยากต่อการตรวจจับซึ่งจะปรากฏก็ต่อเมื่อนำทางไปยังหน้าจอนั้นๆ เท่านั้น
Modal Screens และตัวเลือกการแสดงผล
Modal ใน Expo Router คือหน้าจอปกติที่กำหนดค่า presentation: 'modal' ไว้ใน layout แนวทางนี้ยังคงยึดตามหลักการของ file-based routing กล่าวคือ modal ก็เป็นเพียง route หนึ่งเช่นเดียวกับ route อื่นๆ
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
{/* Modal screen slides up from the bottom */}
<Stack.Screen
name="create-post"
options={{
presentation: 'modal',
headerTitle: 'New Post',
}}
/>
</Stack>
)
}import { View, TextInput, Button, StyleSheet } from 'react-native'
import { router } from 'expo-router'
import { useState } from 'react'
export default function CreatePostModal() {
const [title, setTitle] = useState('')
const handleSubmit = () => {
// Submit logic here
router.back() // Dismiss the modal
}
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Post title"
value={title}
onChangeText={setTitle}
/>
<Button title="Publish" onPress={handleSubmit} />
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
input: {
borderWidth: 1,
borderColor: '#333',
borderRadius: 8,
padding: 12,
fontSize: 16,
marginBottom: 16,
},
})เมื่อเข้าถึง /create-post หน้าจอจะแสดงในรูปแบบ modal เลื่อนขึ้นจากด้านล่าง การเรียก router.back() จะปิด modal และนำผู้ใช้กลับไปยังหน้าจอก่อนหน้าใน navigation stack
การนำทางแบบ Programmatic และ Router API
นอกเหนือจากคอมโพเนนต์ Link แล้ว Expo Router ยังมี imperative API ผ่านออบเจ็กต์ router ซึ่งช่วยจัดการ navigation ที่ถูกเรียกจาก business logic แทนการกดปุ่มของผู้ใช้โดยตรง
import { router } from 'expo-router'
// Push a new screen onto the stack
router.push('/profile/settings')
// Replace the current screen (no back button)
router.replace('/login')
// Go back to the previous screen
router.back()
// Navigate with parameters
router.push({
pathname: '/product/[id]',
params: { id: '42', source: 'recommendations' },
})
// Check if going back is possible
import { useRouter } from 'expo-router'
function BackButton() {
const router = useRouter()
return router.canGoBack() ? (
<Button title="Back" onPress={() => router.back()} />
) : null
}ความแตกต่างระหว่าง router.push กับ router.replace มีความสำคัญอย่างยิ่งในกระบวนการ authentication ของ React Native หลังจากเข้าสู่ระบบสำเร็จ การใช้ router.replace('/dashboard') จะป้องกันไม่ให้ผู้ใช้กลับไปยังหน้าจอเข้าสู่ระบบได้ด้วยปุ่มย้อนกลับ
router.replace() จะแทนที่รายการปัจจุบันใน navigation history สำหรับ authentication guard ที่ต้อง redirect ผู้ใช้ที่ยังไม่ได้เข้าสู่ระบบ ควรใช้ <Redirect href="/login" /> ในส่วน render ของคอมโพเนนต์แทน เนื่องจากจะทำงานในขั้นตอน render และรองรับ server-side rendering บนเว็บได้อย่างถูกต้อง
Middleware และการป้องกันเส้นทาง
Expo Router มี server middleware สำหรับ logic ที่ต้องทำงานก่อนจะถึงคอมโพเนนต์ของ route ไฟล์ +middleware.ts จะดักจับ request ก่อนที่จะถึง route component ทำให้สามารถตรวจสอบ authentication, redirect และจัดการ HTTP headers ได้
import { type MiddlewareRequest } from 'expo-router/server'
export function middleware(request: MiddlewareRequest) {
const { pathname } = request.nextUrl
// Protect dashboard routes
const protectedPaths = ['/dashboard', '/settings', '/profile']
const isProtected = protectedPaths.some(p => pathname.startsWith(p))
if (isProtected) {
const token = request.cookies.get('session')
if (!token) {
return Response.redirect(new URL('/login', request.url))
}
}
return undefined // Continue to route
}บนแพลตฟอร์ม native นั้น server middleware จะไม่ทำงาน การป้องกัน route จึงต้องอาศัย guard ฝั่ง client แทน รูปแบบที่นิยมมากที่สุดคือการครอบ layout ที่ต้องการป้องกันด้วยการตรวจสอบ authentication:
import { Redirect, Stack } from 'expo-router'
import { useAuth } from '@/hooks/useAuth'
export default function AuthenticatedLayout() {
const { isLoggedIn, isLoading } = useAuth()
if (isLoading) return null
if (!isLoggedIn) return <Redirect href="/login" />
return <Stack />
}Server middleware ทำงานเฉพาะบนเว็บที่มีการใช้ server-side rendering เท่านั้น สำหรับแอปพลิเคชัน native จำเป็นต้องสร้าง guard ฝั่ง client ไว้ใน layout component การใช้ทั้งสองแนวทางร่วมกันจะรับประกันการป้องกันที่ครอบคลุมบนทุกแพลตฟอร์ม
การ Migrate SDK 56: Fork React Navigation
ตั้งแต่ Expo SDK 56 Expo Router ไม่ได้พึ่งพา package @react-navigation/* อีกต่อไป หากโปรเจกต์มีการ import จาก React Navigation โดยตรง ต้องอัปเดต import เหล่านั้น:
| Package เดิม | ตำแหน่งใหม่ |
|---|---|
@react-navigation/native | expo-router/react-navigation |
@react-navigation/stack | expo-router/js-stack |
@react-navigation/bottom-tabs | expo-router/js-tabs |
รัน codemod เพื่ออัปเดต import โดยอัตโนมัติ:
npx expo-codemod sdk-56-expo-router-react-navigation-replace srcRuntime API ยังคงเหมือนเดิม เปลี่ยนเพียง module specifier เท่านั้น Library จาก third-party ที่ยังคง import จาก @react-navigation/core จะทำงานได้โดยอัตโนมัติเนื่องจาก Expo CLI จะ rewrite import เหล่านั้นระหว่างการ bundling
แหล่งข้อมูลอ้างอิง
- เอกสาร Expo Router - คู่มือและเอกสาร API อย่างเป็นทางการ
- Changelog Expo SDK 57 - บันทึกการเผยแพร่เวอร์ชันเสถียรปัจจุบัน
- Blog Post Expo Router v56 - คำอธิบายการ fork React Navigation
- คู่มือ Migrate SDK 55 ไปยัง 56 - ขั้นตอนการ migrate import
สิ่งที่ผู้สัมภาษณ์คาดหวังเกี่ยวกับ Expo Router
- Expo Router map แต่ละไฟล์ในไดเรกทอรี
appไปยัง route โดยอัตโนมัติ ขจัดการตั้งค่า navigation แบบ manual - Layout ที่กำหนดผ่านไฟล์
_layout.tsxสร้างโครงสร้าง navigation สำหรับ stack, tab และ drawer - Dynamic routes ด้วย
[param].tsxและ catch-all routes ด้วย[...slug].tsxจัดการ navigation ที่มี parameter พร้อม TypeScript support อย่างเต็มรูปแบบ - Typed routes ตรวจจับลิงก์ navigation ที่เสียตั้งแต่ขั้นตอน compilation เมื่อเปิดใช้ฟีเจอร์นี้ใน
app.json - Modal screens, programmatic navigation ผ่าน
router.push/replace/backและ server middleware เป็นเครื่องมือครบชุดสำหรับระบบ routing - Route groups ที่ใช้ไดเรกทอรีล้อมด้วยวงเล็บช่วยจัดระเบียบโค้ดโดยไม่กระทบต่อ URL
- Guard ฝั่ง client ใน layout จัดการ authentication บน native ในขณะที่ server middleware ครอบคลุมฝั่งเว็บ
- ตั้งแต่ SDK 56 import จาก
@react-navigation/*ต้องอัปเดตเป็นexpo-router/*ที่เทียบเท่า
สำหรับเนื้อหาเพิ่มเติมเกี่ยวกับสถาปัตยกรรม React Native และการเตรียมตัวสัมภาษณ์ ดูที่ module native modules
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
คุณหาบั๊กใน React Native เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 6 ตุลาคม 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

คู่มือพัฒนาแอป React Native 2026: สร้างแอป Production และคำถามสัมภาษณ์
คู่มือครบถ้วนสำหรับการพัฒนาแอป React Native 2026 ครอบคลุม New Architecture, Expo SDK 56, Hermes V1 และคำถามสัมภาษณ์สำหรับนักพัฒนา React Native

การพัฒนาแอปพลิเคชัน React Native 2026: คู่มือฉบับสมบูรณ์และคำถามสัมภาษณ์
คู่มือครบถ้วนสำหรับการพัฒนาแอปพลิเคชัน React Native ในปี 2026 เรียนรู้ New Architecture, JSI, Fabric, TurboModules และคำถามสัมภาษณ์ที่พบบ่อย

Hermes V1 ใน React Native 0.84: ประสิทธิภาพ, Bytecode Precompiled และคำถามสัมภาษณ์
คู่มือเชิงลึกเกี่ยวกับ JavaScript engine Hermes V1 ใน React Native 0.84 ครอบคลุมการคอมไพล์ bytecode, garbage collector Hades, กลยุทธ์การเพิ่มประสิทธิภาพหน่วยความจำ และคำถามสัมภาษณ์ทางเทคนิค