Server Components vs Client Components
Bài 3 – Phân biệt Server Components và Client Components trong Next.js
Server Components vs Client Components
Trong App Router của Next.js, mỗi component mặc định là Server Component. Khi cần tương tác phía client, trạng thái, hoặc các hook của React, ta dùng Client Component. Việc phân tách này ảnh hưởng trực tiếp đến hiệu năng, kiến trúc và chi phí vận hành.
1. Server Components là gì?
Server Components là components chạy ở server, chỉ gửi HTML về client mà không gửi JavaScript:
- Không gửi JS bundle: Giảm kích thước file tải về
- Direct database access: Có thể query database trực tiếp
- No hydration needed: Không cần hydration ở client
- SEO-friendly: HTML có sẵn từ server
Ý nghĩa trọng tâm:
Server Components giúp giảm bundle size và tăng performance đáng kể.
2. Client Components là gì?
Client Components là components chạy ở browser, cần thiết cho:
- Event handlers: Click, submit, input changes
- State management: useState, useReducer, context
- Browser APIs: localStorage, geolocation, media
- React hooks: useEffect, useRef, custom hooks
Ý nghĩa trọng tâm:
Client Components cần thiết cho interactivity và dynamic behavior.
3. So sánh chi tiết
3.1. Server Components (mặc định)
// app/products/page.tsx - Server Component
export default async function ProductsPage() {
// ✅ Direct database query
const products = await db.products.findMany()
// ✅ No JavaScript sent to client
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
Đặc điểm:
- Không cần directive đặc biệt
- Có thể dùng async/await
- Không thể dùng event handlers
- Không thể dùng browser APIs
3.2. Client Components (cần “use client”)
// components/Counter.tsx - Client Component
"use client"
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
// ✅ Event handlers work
const increment = () => setCount(count + 1)
return (
<button onClick={increment}>
Count: {count}
</button>
)
}
Đặc điểm:
- Cần thêm
"use client"ở đầu file - Có thể dùng tất cả React hooks
- Có thể dùng browser APIs
- Gửi JavaScript bundle đến client
4. Khi nào dùng Server Components?
✅ Nên dùng Server Components khi:
- Fetch data từ database/API: Không cần client-side fetching
- Render static content: Blog posts, product listings
- SEO quan trọng: HTML có sẵn từ server
- Performance critical: Giảm bundle size
- Security: Giữ sensitive logic ở server
✅ Ví dụ Server Components thực tế:
// app/blog/[slug]/page.tsx
export default async function BlogPost({
params
}: {
params: { slug: string }
}) {
// ✅ Fetch data directly in component
const post = await getBlogPost(params.slug)
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
<AuthorInfo authorId={post.authorId} />
</article>
)
}
5. Khi nào dùng Client Components?
✅ Nên dùng Client Components khi:
- User interactions: Click, hover, drag & drop
- Form handling: Validation, submission
- Real-time updates: WebSocket, polling
- Browser APIs: localStorage, geolocation
- Third-party libraries: Charts, maps, editors
✅ Ví dụ Client Components thực tế:
// components/SearchBox.tsx
"use client"
import { useState } from 'react'
import { useRouter } from 'next/navigation'
export function SearchBox() {
const [query, setQuery] = useState('')
const router = useRouter()
const handleSearch = (e: FormEvent) => {
e.preventDefault()
router.push(`/search?q=${encodeURIComponent(query)}`)
}
return (
<form onSubmit={handleSearch}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search products..."
/>
<button type="submit">Search</button>
</form>
)
}
6. Kết hợp Server và Client Components
6.1. Server Component chứa Client Component
// app/products/page.tsx - Server Component
import { SearchBox } from '@/components/SearchBox'
import { ProductList } from './ProductList'
export default async function ProductsPage() {
const products = await getProducts()
return (
<div>
{/* ✅ Client Component trong Server Component */}
<SearchBox />
{/* ✅ Server Component khác */}
<ProductList products={products} />
</div>
)
}
6.2. Props truyền từ Server sang Client
// Server Component
export default async function Page() {
const products = await getProducts()
// ✅ Truyền props serializable
return <ClientComponent products={products} />
}
// Client Component
"use client"
export function ClientComponent({ products }) {
// ✅ Nhận props từ Server Component
return <div>{products.length} products</div>
}
7. Best Practices
7.1. Ưu tiên Server Components
// ✅ Tốt: Server Component mặc định
export default function ProductPage() {
return <div>Static content</div>
}
// ❌ Không cần thiết: Client Component cho static content
"use client"
export default function ProductPage() {
return <div>Static content</div>
}
7.2. Tách biệt rõ ràng
// ✅ Tốt: Tách Server và Client logic
// app/page.tsx - Server Component
export default async function Page() {
const data = await fetchData()
return <InteractiveComponent data={data} />
}
// components/InteractiveComponent.tsx - Client Component
"use client"
export function InteractiveComponent({ data }) {
const [selected, setSelected] = useState(null)
return (
<div>
{data.map(item => (
<button key={item.id} onClick={() => setSelected(item)}>
{item.name}
</button>
))}
{selected && <div>Selected: {selected.name}</div>}
</div>
)
}
8. Bài tập thực hành
Bài tập 1
Tạo Server Component lấy dữ liệu từ API và hiển thị danh sách:
// app/posts/page.tsx
export default async function PostsPage() {
const posts = await fetch('https://jsonplaceholder.typicode.com/posts')
.then(r => r.json())
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</article>
))}
</div>
)
}
Bài tập 2
Tạo Client Component “Counter” và nhúng vào trang server:
// components/Counter.tsx
"use client"
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
// app/page.tsx
import { Counter } from '@/components/Counter'
export default function HomePage() {
return (
<div>
<h1>Home Page</h1>
<Counter />
</div>
)
}
Bài tập 3
Đo sự khác biệt bundle bằng cách:
- Build app với chỉ Server Components
- Thêm Client Component và build lại
- So sánh kích thước file JS trong Network tab
9. Kết luận bài học
- Server Components: Mặc định, performance tốt, SEO-friendly
- Client Components: Cần
"use client", dùng cho interactivity - Kết hợp đúng: Server cho data + static, Client cho interactions
- Ưu tiên Server: Luôn bắt đầu với Server Components
🔑 GHI NHỚ QUAN TRỌNG:
- Server Components là mặc định trong App Router
- Client Components cần
"use client"directive- Server = Performance, Client = Interactivity
- Luôn ưu tiên Server Components khi có thể