🔗 บทที่ 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 ในบทถัดไป