Gensics

Back to Home

Next.js 14: App Router และ Server Components

Next.js Developer16 ธันวาคม 2567Next.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

  1. Server Components by default - ประสิทธิภาพดีขึ้น
  2. Built-in Loading States - UX ที่ดีขึ้น
  3. Streaming Support - แสดงเนื้อหาแบบค่อยเป็นค่อยไป
  4. Improved SEO - metadata และ sitemap generation
  5. 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