React 19 và Next.js 15: Khi nào dùng Server Components, khi nào dùng Client Components

2 tháng trước · 8 phút đọc
Mình từng làm sai trong 3 tháng đầu dùng Next.js 13
Hồi App Router mới ra, mình hứng chí convert toàn bộ app sang Server Components. Kết quả? Form không hoạt động, state bị mất, và cái button onClick không chịu chạy. Mình mất gần một tuần debug trước khi nhận ra mình đang hiểu sai hoàn toàn cách hai loại component này hoạt động.
React 19 và Next.js 15 đẩy Server Components (RSC) lên một tầm cao mới - streaming, Server Actions, partial prerendering. Nhưng nếu bạn không nắm rõ ranh giới giữa server và client, bạn sẽ gặp đúng cái lỗi mình từng gặp.
Bài này mình sẽ giải thích đúng một câu hỏi: component này nên chạy ở đâu? Kèm code thực tế cho từng trường hợp.
Server Components và Client Components khác nhau thế nào?
Đây là điểm nhiều người hiểu lơ mơ nhất.
Server Components chạy hoàn toàn trên server - lúc build hoặc lúc request. Chúng không có lifecycle, không có state, không có event handler. Đổi lại, chúng có thể đọc database trực tiếp, giữ secrets an toàn, và gửi về browser chỉ HTML thuần - không kèm JavaScript bundle.
Client Components là React component truyền thống bạn đã biết. Chúng được hydrate trên browser, có useState, useEffect, xử lý click, scroll, form input. Khi bạn thêm 'use client' ở đầu file, bạn đang nói với Next.js: "bundle cái này vào JavaScript gửi xuống browser".
Server không gửi JS bundle - Client mới cần hydration
Một chi tiết quan trọng hay bị bỏ qua: mặc định trong Next.js 15, mọi component đều là Server Component. Bạn phải opt-in vào Client Component bằng 'use client'. Điều này ngược với cách React hoạt động trước đây.
Và 'use client' không có nghĩa là "chỉ chạy trên client". Nó vẫn được server-render lần đầu (SSR), sau đó hydrate trên browser. Đây là nguồn gốc của phần lớn nhầm lẫn.
Khi nào dùng Server Components?
Nguyên tắc đơn giản: nếu component chỉ cần hiển thị dữ liệu và không cần tương tác người dùng, dùng Server Component.
Các trường hợp điển hình:
- Data fetching: Blog posts, product listings, user profiles - bất kỳ thứ gì cần query từ DB hoặc API
- Static content: Header, footer, layout, sidebar
- SEO-critical content: Nội dung cần crawler đọc được ngay
- Sensitive logic: Xử lý API keys, kiểm tra auth token, business logic không nên lộ trên client
Server Component phù hợp nhất khi dữ liệu đến từ DB và không cần tương tác
Ví dụ kiến trúc blog:
Lợi ích rõ ràng nhất: không còn cái pattern useEffect + fetch + useState lòng vòng nữa. Dữ liệu đến thẳng từ server, render xong gửi HTML xuống browser. Trang load nhanh hơn và SEO tốt hơn vì content có ngay từ đầu.
Một lưu ý: Next.js 15 thay đổi caching behavior - fetch không còn cache mặc định như Next.js 14. Bạn cần explicit opt-in:
Khi nào dùng Client Components?
Thêm 'use client' khi component cần một trong ba thứ: state, browser APIs, hoặc event handlers.
Cụ thể:
useState,useReducer,useContext- bất kỳ reactive state nàouseEffect,useLayoutEffect- side effects phía browser- Event handlers:
onClick,onChange,onSubmit - Browser-only APIs:
window,document,localStorage,navigator - Third-party libraries chưa hỗ trợ RSC: animation libraries, charting libraries, form libraries
Thêm 'use client' chỉ khi thực sự cần - đừng lạm dụng
Một pattern thực tế cho dashboard với realtime data:
Và một lưu ý quan trọng về component tree: khi bạn đánh dấu một component là 'use client', toàn bộ subtree bên dưới nó cũng trở thành Client Component - dù bạn có viết 'use server' hay không. Vì vậy hãy đặt boundary 'use client' càng thấp trong cây component càng tốt.
Pattern được khuyến nghị là "leaf components" - chỉ những component ngoài cùng (button, input, chart) mới cần 'use client', còn parent layout vẫn là Server Component.
Server Actions: data mutation không cần API route
React 19 và Next.js 15 giới thiệu Server Actions như một cách chính thức để xử lý mutations (tạo/sửa/xóa dữ liệu) mà không cần tạo API endpoint riêng.
Thay vì pattern cũ fetch('/api/submit', { method: 'POST' }), giờ bạn viết function trực tiếp chạy trên server:
Server Actions xử lý mutation thẳng trên server - không cần API route trung gian
Nếu cần feedback ngay lập tức (loading state, optimistic update), kết hợp với useActionState từ React 19:
Trade-off cần biết: Server Actions không phải silver bullet. Nếu bạn cần gọi cùng mutation từ nhiều nơi (mobile app, third-party service), API route truyền thống vẫn linh hoạt hơn. Server Actions phù hợp nhất cho web-only mutations trong cùng Next.js app.
Streaming và Suspense: tránh waterfall trong Next.js 15
Vấn đề phổ biến khi migrate sang App Router: bạn có một trang cần nhiều data sources khác nhau - user info, posts, notifications. Nếu fetch tuần tự, người dùng nhìn màn hình trắng trong khi chờ cả ba.
Next.js 15 giải quyết điều này bằng Streaming kết hợp với Suspense.
Streaming render từng phần - người dùng thấy content sớm hơn thay vì chờ toàn bộ trang
Mỗi Suspense boundary stream độc lập. UserProfile load xong trước thì hiện trước, không cần chờ RecentPosts. Người dùng thấy content dần dần thay vì nhìn skeleton mãi.
Một trick để tránh waterfall: nếu nhiều Server Components trong cùng scope cần fetch data, dùng Promise.all để fetch song song:
