เจาะลึก Next.js 14: คู่มือ App Router และ Server Components สำหรับมือใหม่
Next.js Developer•16 ธันวาคม 2567•Next.js
Next.jsApp RouterServer ComponentsReactNext.js 14
Next.js 14 มาพร้อมกับ App Router ที่ปรับปรุงใหม่และ Server Components ที่ช่วยปรับปรุงประสิทธิภาพ
App Router Structure
App Router ใช้โครงสร้างแบบใหม่ที่อิงตาม file system:
app/
├── layout.js // Root layout
├── page.js // Home page
├── loading.js // Loading UI
├── error.js // Error UI
├── blog/
│ ├── layout.js // Blog layout
│ ├── page.js // Blog listing
│ └── [slug]/
│ └── page.js // Blog post
└── api/
└── posts/
└── route.js // API endpoint
Server Components (Default)
Server Components ทำงานบน server และส่ง HTML ที่ render แล้วไปยัง client:
// app/blog/page.js - Server Component import { getPosts } from '@/lib/posts'; export default async function BlogPage() { // Data fetching บน server const posts = await getPosts(); return ( <div> <h1>Blog Posts</h1> {posts.map(post => ( <article key={post.id}> <h2>{post.title}</h2> <p>{post.excerpt}</p> </article> ))} </div> ); } // Metadata สำหรับ SEO export const metadata = { title: 'Blog - My Website', description: 'Latest blog posts about web development' };
Client Components
ใช้ 'use client' directive เมื่อต้องการ interactivity:
// components/LikeButton.js - Client Component 'use client'; import { useState } from 'react'; export default function LikeButton({ initialLikes }) { const [likes, setLikes] = useState(initialLikes); const [isLiked, setIsLiked] = useState(false); const handleLike = async () => { if (!isLiked) { setLikes(likes + 1); setIsLiked(true); // API call to update likes await fetch('/api/like', { method: 'POST', body: JSON.stringify({ postId: '...' }) }); } }; return ( <button onClick={handleLike} className={isLiked ? 'liked' : ''} > 👍 {likes} </button> ); }
Data Fetching Patterns
1. Server-side Data Fetching
// app/posts/[id]/page.js import { getPost } from '@/lib/posts'; import { notFound } from 'next/navigation'; export default async function PostPage({ params }) { const post = await getPost(params.id); if (!post) { notFound(); // แสดง 404 page } return ( <article> <h1>{post.title}</h1> <div dangerouslySetInnerHTML={{ __html: post.content }} /> </article> ); } // Generate static paths export async function generateStaticParams() { const posts = await getPosts(); return posts.map(post => ({ id: post.id.toString() })); }
2. Streaming และ Suspense
// app/dashboard/page.js import { Suspense } from 'react'; import UserStats from './UserStats'; import RecentPosts from './RecentPosts'; import Analytics from './Analytics'; export default function Dashboard() { return ( <div> <h1>Dashboard</h1> {/* แสดงเนื้อหาแบบ streaming */} <Suspense fallback={<div>Loading user stats...</div>}> <UserStats /> </Suspense> <Suspense fallback={<div>Loading posts...</div>}> <RecentPosts /> </Suspense> <Suspense fallback={<div>Loading analytics...</div>}> <Analytics /> </Suspense> </div> ); }
API Routes ใน App Router
// app/api/posts/route.js import { NextResponse } from 'next/server'; import { getPosts, createPost } from '@/lib/posts'; // GET /api/posts export async function GET(request) { try { const { searchParams } = new URL(request.url); const page = searchParams.get('page') || '1'; const posts = await getPosts({ page: parseInt(page) }); return NextResponse.json(posts); } catch (error) { return NextResponse.json( { error: 'Failed to fetch posts' }, { status: 500 } ); } } // POST /api/posts export async function POST(request) { try { const body = await request.json(); const post = await createPost(body); return NextResponse.json(post, { status: 201 }); } catch (error) { return NextResponse.json( { error: 'Failed to create post' }, { status: 500 } ); } }
Layouts และ Templates
Root Layout
// app/layout.js import './globals.css'; export const metadata = { title: { template: '%s | My Blog', default: 'My Blog' }, description: 'A blog about web development' }; export default function RootLayout({ children }) { return ( <html lang="th"> <body> <header> <nav>{/* Navigation */}</nav> </header> <main>{children}</main> <footer>{/* Footer */}</footer> </body> </html> ); }
Nested Layout
// app/blog/layout.js export default function BlogLayout({ children }) { return ( <div className="blog-layout"> <aside className="sidebar"> {/* Blog sidebar */} </aside> <div className="content"> {children} </div> </div> ); }
ข้อดีของ App Router
- Server Components by default - ประสิทธิภาพดีขึ้น
- Built-in Loading States - UX ที่ดีขึ้น
- Streaming Support - แสดงเนื้อหาแบบค่อยเป็นค่อยไป
- Improved SEO - metadata และ sitemap generation
- Better Developer Experience - TypeScript support ดีขึ้น
Migration จาก Pages Router
// เดิม (Pages Router) // pages/blog/[slug].js export default function BlogPost({ post }) { return <div>{post.title}</div>; } export async function getStaticProps({ params }) { const post = await getPost(params.slug); return { props: { post } }; } // ใหม่ (App Router) // app/blog/[slug]/page.js export default async function BlogPost({ params }) { const post = await getPost(params.slug); return <div>{post.title}</div>; }
สรุป
App Router ใน Next.js 14 ให้ประสิทธิภาพและ developer experience ที่ดีขึ้นผ่าน Server Components, Streaming, และ improved data fetching patterns