🔗 บทที่ 4: Link และ Navigation
เรียนรู้การนำทางระหว่างหน้าด้วย Next.js Link component และ navigation hooks ใน Next.js 15
วัตถุประสงค์การเรียนรู้
เข้าใจการทำงานของ Next.js Link component ใน Next.js 15
รู้จักความแตกต่างระหว่าง Link และ <a> tag ปกติ
สามารถใช้ navigation hooks สำหรับจัดการ routing
เข้าใจ prefetching และ fresh data behavior ใน Next.js 15
🤔 Navigation ใน Next.js คืออะไร?
Navigation ใน Next.js หมายถึงการเปลี่ยนหน้าระหว่าง routes ต่างๆ โดยไม่ต้องโหลดหน้าเว็บใหม่ทั้งหมด ทำให้การใช้งานเร็วและลื่นไหลมากขึ้น พร้อมกับ fresh data ใน Next.js 15
⚡ เปรียบเทียบ Traditional vs Next.js Navigation
คุณสมบัติ | Traditional HTML | Next.js |
---|---|---|
การโหลดหน้า | Full page reload | Client-side navigation |
ความเร็ว | ช้า (ดาวน์โหลดใหม่) | เร็ว (prefetch แต่ไม่ cache by default ใน 15) |
SEO | ดี | ดีมาก |
User Experience | มี loading ระหว่างหน้า | เปลี่ยนหน้าไม่มี loading (แต่ fresh data) |
JavaScript Bundle | ดาวน์โหลดทุกครั้ง | Shared และ cached |
Data Freshness (Next.js 15) | Fresh data ทุกครั้ง | Fresh data by default (ไม่ cache page) |
🌟 ฟีเจอร์ของ Next.js Link
Prefetching (Enhanced)
โหลดข้อมูลล่วงหน้าสำหรับ links ที่อยู่ใน viewport (ปรับปรุงใน Next.js 15)
ตัวอย่าง: เมื่อ Link ปรากฏบนหน้าจอ Next.js จะ prefetch route นั้นโดยอัตโนมัติ
Client-side Navigation
เปลี่ยนหน้าโดยไม่ต้องโหลดทั้งหน้าใหม่ พร้อม fresh data
ตัวอย่าง: ใช้ History API เพื่อเปลี่ยน URL โดยไม่ refresh หน้า (data จะ fresh ใน Next.js 15)
Code Splitting
โหลดเฉพาะโค้ดที่จำเป็นสำหรับหน้านั้นๆ ด้วย Turbopack
ตัวอย่าง: แต่ละ route จะมี JavaScript bundle แยกกัน (เร็วขึ้นด้วย Turbopack)
State Preservation
รักษา state ของ layout components แต่ page data จะ fresh
ตัวอย่าง: layout state จะคงอยู่ แต่ page components จะได้ข้อมูลใหม่ (Next.js 15)
📖 การใช้ Link Component
ตัวอย่างการใช้ Link component แบบง่ายๆ
// app/components/Navigation.tsx import Link from 'next/link'; export default function Navigation() { return ( <nav> <ul> <li> <Link href="/">หน้าแรก</Link> </li> <li> <Link href="/about">เกี่ยวกับเรา</Link> </li> <li> <Link href="/products">สินค้า</Link> </li> <li> <Link href="/contact">ติดต่อเรา</Link> </li> </ul> </nav> ); }
การใช้ Link กับ dynamic routes และการส่งพารามิเตอร์
// app/components/ProductList.tsx import Link from 'next/link'; interface Product { id: string; name: string; price: number; } export default function ProductList({ products }: { products: Product[] }) { return ( <div> <h2>รายการสินค้า</h2> {products.map(product => ( <div key={product.id}> <h3>{product.name}</h3> <p>ราคา: {product.price} บาท</p> {/* Dynamic route */} <Link href={`/products/${product.id}`}> ดูรายละเอียด </Link> </div> ))} </div> ); }
🔧 useRouter Hook
useRouter hook ใช้สำหรับ programmatic navigation และเข้าถึงข้อมูล router
'use client'; import { useRouter } from 'next/navigation'; export default function NavigationButtons() { const router = useRouter(); const handleGoBack = () => { router.back(); // กลับหน้าก่อนหน้า }; const handleGoForward = () => { router.forward(); // ไปหน้าถัดไป }; const handleGoHome = () => { router.push('/'); // ไปหน้าแรก }; const handleReplace = () => { router.replace('/dashboard'); // replace current page }; const handleRefresh = () => { router.refresh(); // refresh current page }; return ( <div> <button onClick={handleGoBack}>← ย้อนกลับ</button> <button onClick={handleGoForward}>ถัดไป →</button> <button onClick={handleGoHome}>🏠 หน้าแรก</button> <button onClick={handleReplace}>📊 Dashboard</button> <button onClick={handleRefresh}>🔄 รีเฟรช</button> </div> ); }
'use client'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; export default function LoginForm() { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); try { // ส่งข้อมูล login const response = await fetch('/api/auth/login', { method: 'POST', // ... login logic }); if (response.ok) { // Login สำเร็จ - redirect ไป dashboard router.push('/dashboard'); } else { // Login ไม่สำเร็จ - แสดง error alert('Login failed'); } } catch (error) { console.error('Login error:', error); } finally { setIsLoading(false); } }; return ( <form onSubmit={handleSubmit}> {/* form fields */} <button type="submit" disabled={isLoading}> {isLoading ? 'กำลังเข้าสู่ระบบ...' : 'เข้าสู่ระบบ'} </button> </form> ); }
📍 usePathname Hook
usePathname hook ใช้สำหรับดึงข้อมูล current pathname และสร้าง active states
'use client'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; const navItems = [ { href: '/', label: 'หน้าแรก' }, { href: '/about', label: 'เกี่ยวกับเรา' }, { href: '/products', label: 'สินค้า' }, { href: '/contact', label: 'ติดต่อเรา' } ]; export default function ActiveNavigation() { const pathname = usePathname(); return ( <nav> <ul className="flex space-x-4"> {navItems.map((item) => { const isActive = pathname === item.href; return ( <li key={item.href}> <Link href={item.href} className={`px-3 py-2 rounded ${ isActive ? 'bg-blue-500 text-white' : 'text-gray-700 hover:bg-gray-100' }`} > {item.label} </Link> </li> ); })} </ul> </nav> ); }
'use client'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; export default function AdvancedNavigation() { const pathname = usePathname(); const isActive = (path: string) => { // Exact match if (path === pathname) return true; // Starts with (for nested routes) if (path !== '/' && pathname.startsWith(path)) return true; return false; }; const getBreadcrumbs = () => { const segments = pathname.split('/').filter(Boolean); return segments.map((segment, index) => { const path = '/' + segments.slice(0, index + 1).join('/'); return { path, label: segment }; }); }; return ( <div> {/* Main Navigation */} <nav> <Link href="/" className={isActive('/') ? 'active' : ''} > หน้าแรก </Link> <Link href="/products" className={isActive('/products') ? 'active' : ''} > สินค้า </Link> </nav> {/* Breadcrumbs */} <div className="breadcrumbs"> <Link href="/">Home</Link> {getBreadcrumbs().map((crumb, index) => ( <span key={crumb.path}> {' > '} <Link href={crumb.path}>{crumb.label}</Link> </span> ))} </div> {/* Current Page Info */} <div> <p>Current pathname: {pathname}</p> <p>Is products page: {pathname.startsWith('/products')}</p> </div> </div> ); }
✋ ฝึกปฏิบัติ: สร้าง Navigation System
ลองทำตามขั้นตอนนี้เพื่อสร้าง navigation system ที่สมบูรณ์
สร้าง Basic Navigation
สร้าง navigation component พื้นฐาน:
// app/components/Navigation.tsx 'use client'; import Link from 'next/link'; export default function Navigation() { return ( <nav style={{ padding: '1rem', borderBottom: '1px solid #eee' }}> <ul style={{ display: 'flex', gap: '1rem', listStyle: 'none' }}> <li><Link href="/">หน้าแรก</Link></li> <li><Link href="/about">เกี่ยวกับ</Link></li> <li><Link href="/products">สินค้า</Link></li> <li><Link href="/contact">ติดต่อ</Link></li> </ul> </nav> ); }
เพิ่ม Active States
เพิ่ม Programmatic Navigation
⚡ Performance และ Advanced Tips
🚀 Prefetching Strategies
// การควบคุม prefetching <Link href="/products" prefetch={false}> หน้าสินค้า (ไม่ prefetch) </Link> // Hover prefetch <Link href="/products" onMouseEnter={() => router.prefetch('/products')} > หน้าสินค้า (prefetch เมื่อ hover) </Link> // Conditional prefetch {isLoggedIn && ( <Link href="/dashboard" prefetch={true}> Dashboard </Link> )}
💡 Best Practices สำคัญ
ใช้ Link component แทน <a> tag เพื่อให้ Next.js จัดการ client-side navigation
ใช้ absolute paths สำหรับ internal links (เริ่มต้นด้วย /)
ระวังการใช้ prefetch={false} เพราะจะลด performance
ใช้ usePathname สำหรับ active states แทน window.location
ใช้ router.replace() สำหรับ redirects ที่ไม่ต้องการ back navigation
จัดการ loading states ด้วย useTransition หรือ loading.tsx
🎯 ยินดีด้วย! คุณเรียนจบบทที่ 4 แล้ว
ตอนนี้คุณเข้าใจการใช้ Link component และ navigation hooks ใน Next.js 15 แล้ว! พร้อมสำหรับการเรียนรู้เรื่อง Layouts และ Templates ในบทถัดไป