Next.js 14: App Router และ Server Components
Next.js Developer•16 ธันวาคม 2567•Next.js
Next.jsApp RouterServer ComponentsReactPerformance
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