Nếu bạn đã làm việc với React và Next.js, chắc hẳn bạn không còn xa lạ với những dòng code useEffect
, useState
để gọi API và quản lý trạng thái loading, error. Đó là một mô hình quen thuộc, nhưng đôi khi lại dài dòng và tiềm ẩn nhiều vấn đề về hiệu năng. Giờ đây, với sự ra đời của App Router và React Server Components (RSC) trong Next.js, cách chúng ta lấy dữ liệu đã thay đổi một cách ngoạn mục.
Hãy cùng nhau khám phá cuộc cách mạng này, một sự thay đổi không chỉ giúp website của bạn nhanh hơn, an toàn hơn mà còn khiến cho việc code của bạn trở nên đơn giản và thú vị hơn bao giờ hết. 🚀
Server Components là gì? Tại sao nó thay đổi cuộc chơi?
Trước khi đi sâu vào data fetching, chúng ta cần hiểu "nhân vật chính": Server Components.
Hãy tưởng tượng thế này:
- Client Components: Là những người phục vụ trong nhà hàng. Họ nhận yêu cầu từ khách (người dùng), chạy đi chạy lại để lấy đồ, và tương tác trực tiếp với khách (xử lý
onClick
,onChange
, quản lýuseState
). Mọi công việc của họ đều diễn ra tại "phòng ăn" (trình duyệt của người dùng). - Server Components: Là những đầu bếp chuyên nghiệp trong bếp. Họ chuẩn bị sẵn mọi nguyên liệu (lấy dữ liệu từ database, gọi API), nấu nướng (render component thành HTML) và chỉ đưa ra món ăn đã hoàn chỉnh. Công việc của họ diễn ra hoàn toàn ở "nhà bếp" (máy chủ), và khách hàng không cần biết quá trình phức tạp bên trong.
Theo mặc định, mọi component trong thư mục app
của Next.js đều là Server Components. Đây là một sự thay đổi tư duy căn bản. Thay vì gửi một "mớ" JavaScript xuống trình duyệt để nó tự đi lấy dữ liệu, giờ đây máy chủ sẽ làm hết việc nặng nhọc này.
Lợi ích cốt lõi của việc này là gì?
- Hiệu năng vượt trội (Performance): Vì logic lấy dữ liệu và render diễn ra trên server, lượng JavaScript gửi xuống client giảm đi đáng kể. Trang web của bạn tải nhanh hơn, mang lại trải nghiệm mượt mà hơn cho người dùng.
- Bảo mật nâng cao (Security): Bạn có thể truy cập trực tiếp vào database, sử dụng các API key, token bí mật ngay trong component mà không sợ bị lộ ra phía client. Mọi thứ nhạy cảm đều được giữ an toàn trên server.
- Code đơn giản và trực quan hơn (Simplicity): Hãy tạm biệt
useEffect
,useState
cho việc fetching. Giờ đây, bạn có thể dùngasync/await
ngay trong component của mình. Code của bạn sẽ gọn gàng và dễ đọc hơn rất nhiều. - Tối ưu SEO (SEO-Friendly): Nội dung được render hoàn toàn trên server, giúp các công cụ tìm kiếm như Google dễ dàng đọc và lập chỉ mục, cải thiện thứ hạng website của bạn.
Các phương pháp Data Fetching với Server Components
Với Server Components, việc lấy dữ liệu trở nên tự nhiên như cách bạn viết code Node.js vậy. Bạn có thể sử dụng fetch
hoặc bất kỳ thư viện nào bạn muốn (prisma
, axios
,...).
Cú pháp cơ bản: async/await
ngay trong Component
Đây là vẻ đẹp của sự đơn giản. Hãy xem ví dụ lấy danh sách bài viết từ một API:
// app/posts/page.jsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
if (!res.ok) {
throw new Error('Failed to fetch data')
}
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<main>
<h1>All Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
Thấy không? Component PostsPage
giờ là một hàm async
. Chúng ta chỉ cần await
dữ liệu và render nó ra. Không useEffect
, không useState
, không loading
. Mọi thứ thật sạch sẽ!
Quyền năng của Caching: Tối ưu hóa tốc độ
Next.js đã mở rộng API fetch
mặc định để cung cấp khả năng kiểm soát caching vô cùng mạnh mẽ ngay trên server. Đây là chìa khóa để cân bằng giữa dữ liệu mới nhất và tốc độ tải trang.
1. Static Fetching (Mặc định) - Lấy dữ liệu tĩnh
Đây là hành vi mặc định. Next.js sẽ tự động fetch dữ liệu tại thời điểm build và cache lại kết quả. Lần truy cập tiếp theo sẽ sử dụng lại dữ liệu đã cache này. Rất lý tưởng cho những dữ liệu ít thay đổi như bài viết blog, trang giới thiệu sản phẩm.
// Dữ liệu sẽ được fetch lúc build và cache vô thời hạn
fetch('https://...')
2. Revalidating Data - Tự động làm mới dữ liệu (ISR)
Bạn muốn dữ liệu được làm mới sau một khoảng thời gian nhất định? Hãy sử dụng tùy chọn next: { revalidate: <số giây> }
. Đây chính là Incremental Static Regeneration (ISR) được tích hợp sẵn.
// Dữ liệu sẽ được cache và tự động làm mới sau mỗi 60 giây
fetch('https://...', { next: { revalidate: 60 } })
Trang của bạn vừa có tốc độ của trang tĩnh, vừa đảm bảo dữ liệu không bao giờ quá cũ.
3. Dynamic Fetching - Luôn lấy dữ liệu mới nhất (SSR)
Với những dữ liệu cần phải luôn mới nhất trong mỗi lần request (ví dụ: giỏ hàng, thông tin người dùng đang đăng nhập), bạn chỉ cần tắt cache đi.
// Fetch dữ liệu mới nhất trên mỗi request, không sử dụng cache
fetch('https://...', { cache: 'no-store' })
Việc này tương đương với getServerSideProps
trong Pages Router, đảm bảo tính động cho trang của bạn.
Xử lý trạng thái Loading và Error một cách thanh lịch
"Vậy còn trạng thái loading
thì sao?" - bạn sẽ hỏi. Server Components kết hợp hoàn hảo với React Suspense để giải quyết vấn đề này.
Bạn có thể tạo một file loading.js
trong cùng một thư mục. Next.js sẽ tự động hiển thị nội dung của file này trong khi dữ liệu của page.js
đang được fetch.
Cấu trúc thư mục:
/app
/posts
/page.jsx <-- Component `async` đang fetch dữ liệu
/loading.jsx <-- Component sẽ hiển thị trong lúc chờ
/error.jsx <-- Component sẽ hiển thị nếu có lỗi
Ví dụ file loading.jsx
:
// app/posts/loading.jsx
export default function Loading() {
return <p>Loading posts, please wait...</p>
}
Tương tự, bạn có thể tạo file error.jsx
để xử lý các lỗi xảy ra trong quá trình fetch dữ liệu một cách mượt mà, tránh làm "sập" cả trang web.
Bằng cách này, bạn có thể stream nội dung về cho người dùng. Giao diện chính của trang sẽ hiển thị ngay lập tức cùng với fallback loading, sau đó khi dữ liệu về đến đâu, nội dung sẽ được điền vào đến đó. Đây là một cải tiến khổng lồ cho trải nghiệm người dùng (UX).
Khi nào cần đến Client Components?
Server Components rất mạnh mẽ cho việc hiển thị dữ liệu, nhưng chúng không thể sử dụng hooks (useState
, useEffect
) hay tương tác với người dùng (onClick
, onChange
).
Đó là lúc Client Components vào cuộc.
Mô hình hoạt động lý tưởng là:
Server Components fetch dữ liệu, Client Components xử lý tương tác.
Hãy tưởng tượng bạn có một danh sách sản phẩm (Server Component) và mỗi sản phẩm có một nút "Thêm vào giỏ hàng" (Client Component).
Bước 1: Fetch dữ liệu trong Server Component (page.jsx
)
// app/products/page.jsx
import AddToCartButton from './AddToCartButton'
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 },
})
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<div>
{products.map((product) => (
<div key={product.id}>
<h2>{product.name}</h2>
<p>{product.price}</p>
{/* Truyền dữ liệu xuống Client Component qua props */}
<AddToCartButton productId={product.id} />
</div>
))}
</div>
)
}
Bước 2: Tạo Client Component để xử lý tương tác (AddToCartButton.jsx
)
// app/products/AddToCartButton.jsx
'use client' // <-- Đánh dấu đây là một Client Component
import { useState } from 'react'
export default function AddToCartButton({ productId }) {
const [isAdding, setIsAdding] = useState(false)
const handleClick = async () => {
setIsAdding(true)
// Logic thêm vào giỏ hàng...
alert(`Product ${productId} added to cart!`)
setIsAdding(false)
}
return (
<button onClick={handleClick} disabled={isAdding}>
{isAdding ? 'Adding...' : 'Add to Cart'}
</button>
)
}
Bằng cách kết hợp cả hai, bạn tận dụng được điểm mạnh của từng loại: Server Components lo việc lấy dữ liệu và render tĩnh, Client Components lo phần tương tác động trên trình duyệt.
Kết luận: Nắm vững các khái niệm mới về Data Fetching
Việc fetching dữ liệu với Server Components trong Next.js không chỉ là một tính năng mới, nó là một sự thay đổi trong tư duy xây dựng ứng dụng web. Bằng cách chuyển phần lớn công việc nặng nhọc về phía máy chủ, chúng ta tạo ra những trang web nhanh hơn, an toàn hơn và dễ bảo trì hơn.
Hãy nắm vững các khái niệm về async
components, chiến lược caching của fetch
, và sự kết hợp mượt mà với Suspense
. Đó chính là chìa khóa để bạn xây dựng những ứng dụng Next.js hiện đại, hiệu năng cao và mang lại trải nghiệm tuyệt vời nhất cho người dùng.
Chúc bạn thành công trên hành trình chinh phục Next.js App Router!