Skip to content

Router

ความจริงคือถ้าเราเขียน React แบบเดิม ๆ เรามักจะกองทุกอย่างไว้ที่ App.tsx แล้วใช้ useState เพื่อสลับหน้าไปมา ซึ่งมัน Scale ยากมาก และเนื่องจาก React เป็น Library ไม่ใช่ Framework (อย่าง Next.js) มันเลยไม่มีระบบจัดการเส้นทาง (Routing) มาให้ในตัว

เพื่อให้เห็นภาพชัดเจนก่อนจะไปถึงเครื่องมือ เรามาแยกให้เห็นก่อนว่าระบบจัดการเส้นทางมันทำงานยังไง และทำไมเราถึง “ขาดมันไม่ได้” เมื่อโปรเจกต์เริ่มโต

ในสมัยก่อน เวลาเราเปลี่ยนหน้า เว็บจะทำการ “โหลดใหม่ทั้งหน้า” (Refresh) ซึ่งทำให้ช้าและเสียจังหวะ แต่พอเป็น Single Page Application (SPA) แบบ React:

  • ไม่มีการโหลดหน้าใหม่: เว็บจะโหลดแค่ครั้งเดียวตอนเข้าครั้งแรก
  • Router เข้ามาจัดการ: เมื่อคุณคลิกเมนู Router จะเปลี่ยน URL ให้ (เช่นจาก / เป็น /about) และสลับเอา Component About มาแปะแทนที่หน้าเดิมทันทีโดยไม่เกิดการกระพริบของหน้าจอ หน้าที่หลักของ Router
    1. Navigation: จัดการการนำทาง เช่น การกดปุ่มย้อนกลับ (Back) หรือไปข้างหน้า (Forward) ใน Browser ให้ทำงานได้ถูกต้อง
    2. State Management (URL): เก็บข้อมูลไว้ที่ URL เช่น /products?category=electronics เพื่อให้เราสามารถก๊อปปี้ลิงก์นี้ไปให้เพื่อน แล้วเพื่อนเปิดมาเจอหน้าเดียวกันเป๊ะๆ
    3. Data Fetching: ใน TanStack Router มันเก่งถึงขั้นที่ว่า พอมันรู้ว่าคุณกำลังจะไปหน้าไหน มันจะแอบไปโหลดข้อมูล (Pre-fetch) เตรียมไว้ให้ก่อนที่คุณจะคลิกเสร็จด้วยซ้ำ!
    4. Security: ตรวจสอบสิทธิ์ (Auth) เช่น ถ้ายังไม่ได้ Login แล้วพยายามจะเข้าหน้า /admin ตัว Router ก็จะเตะเรากลับไปหน้า /login ทันที 🏗️ หน้าตาของ Router ในเชิงเปรียบเทียบ

โดยใน tutorial นี้จะใช้ Tanstack router

คือ Type-safe routing library สำหรับ React ที่สร้างโดยทีม TanStack

จุดเด่น:

  • Type-safe - type inference อัตโนมัติ
  • File-based routing - รองรับ auto-routing จากโครงสร้างไฟล์
  • Lightweight - ไม่มี dependencies มาก

ก่อนจะไปลองใช้งาน เรามาติดตั้ง react tanstack router กันก่อน

Terminal window
npm install @tanstack/react-router @tanstack/router-devtools
npm install -D @tanstack/router-plugin @tanstack/router-cli vite-plugin-tanstack-router

เพื่อให้ระบบ File-based ทำงานได้ลื่นไหล เราต้องลงทะเบียน Plugin ใน Vite

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
TanStackRouterVite(), // วางไว้ก่อน react()
react(),
],
})

เชื่อมต่อ Router ใน src/main.tsx นี่คือขั้นตอนสุดท้าย คือการสร้าง Router Instance และครอบ App ด้วย RouterProvider

src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'
// นำเข้า routeTree ที่ถูกเจนมาอัตโนมัติ (จะเกิดขึ้นหลังรัน dev server)
import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })
// ลงทะเบียน type เพื่อให้ Link ต่างๆ เป็น Fully Type-safe
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
src/
└── routes/
├── __root.tsx # Root layout
├── index.tsx # หน้าแรก (/)
├── catalog.tsx # /catalog
├── cart.tsx # /cart
└── login.tsx # /login

การตั้งค่า Route พื้นฐาน

Section titled “การตั้งค่า Route พื้นฐาน”
// src/routes/__root.tsx — Root layout ของทุกหน้า
import { createRootRoute, Link, Outlet } from "@tanstack/react-router"
export const Route = createRootRoute({
component: () => (
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow-sm">
<div className="max-w-6xl mx-auto px-4 py-3 flex gap-6">
<Link to="/" className="font-bold text-xl text-blue-600">
Adventure Shop
</Link>
<Link to="/catalog" className="text-gray-700 hover:text-blue-600">
สินค้า
</Link>
<Link to="/cart" className="text-gray-700 hover:text-blue-600">
ตะกร้า
</Link>
<Link to="/login" className="text-gray-700 hover:text-blue-600">
เข้าสู่ระบบ
</Link>
</div>
</nav>
<main className="max-w-6xl mx-auto px-4 py-8">
<Outlet />
</main>
</div>
),
})
// src/routes/index.tsx — หน้าแรก
import { createFileRoute } from "@tanstack/react-router"
export const Route = createFileRoute("/")({
component: Index,
})
function Index() {
return (
<div className="text-center py-20">
<h1 className="text-4xl font-bold text-gray-800 mb-4">
ยินดีต้อนรับสู่ Adventure Shop
</h1>
<p className="text-xl text-gray-600 mb-8">
สินค้าสำหรับนักเดินทางทุกประเภท
</p>
<a
href="/catalog"
className="inline-block bg-blue-500 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-600 transition-colors"
>
ดูสินค้า
</a>
</div>
)
}
import { Quiz } from "@/components/quiz"
<Quiz
quiz={{
id: "tanstack-router-basics",
title: "Quiz: TanStack Router พื้นฐาน",
questions: [
{
id: "q1",
type: "single",
question: "TanStack Router มี feature อะไรที่เด่น?",
options: ["Component-based routing", "File-based routing", "API-based routing", "Manual routing"],
correctAnswer: "File-based routing"
},
{
id: "q2",
type: "single",
question: "ถ้าต้องการสร้างหน้า /login ต้องสร้างไฟล์ชื่ออะไรในโฟลเดอร์ routes?",
options: ["login.jsx", "login.tsx", "login.component.tsx", "login.page.tsx"],
correctAnswer: "login.tsx"
},
{
id: "q3",
type: "single",
question: "TanStack Router ใช้ style การเขียนอย่างไร?",
options: ["Imperative style", "Declarative style", "Functional style", "Object-oriented style"],
correctAnswer: "Declarative style"
},
{
id: "q4",
type: "single",
question: "ไฟล์ __root.tsx ใช้ทำอะไร?",
options: ["สร้างหน้า 404", "Root layout ของทุกหน้า", "กำหนด config ของ router", "สร้าง redirect"],
correctAnswer: "Root layout ของทุกหน้า"
},
{
id: "q5",
type: "single",
question: "Component ใดใช้สำหรับสร้าง Link ใน TanStack Router?",
options: ["<Link>", "<a href>", "<RouterLink>", "<NavLink>"],
correctAnswer: "<Link>"
}
]
}}
/>