🔗 บทที่ 4: Link และ Navigation

เรียนรู้การนำทางระหว่างหน้าด้วย Next.js Link component และ navigation hooks ใน Next.js 15
20 นาที
เริ่มต้น
จำเป็น
วัตถุประสงค์การเรียนรู้

เข้าใจการทำงานของ 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 HTMLNext.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 ในบทถัดไป