Next.js 15 và React Server Components: Khi nào dùng Server vs Client Components cho web app 2026

3 ngày trước · 7 phút đọc
Server-first là gì và tại sao Next.js 15 đẩy mạnh hướng này
Mình nhớ lần đầu đọc docs React Server Components - cảm giác như bị reset toàn bộ mental model về React. Cái mình tưởng là "React" suốt mấy năm qua thực ra chỉ là một nửa câu chuyện.
Trước Next.js 13, mọi thứ đều chạy ở client. Bạn fetch data trong useEffect, hiển thị loading spinner, rồi render UI sau khi data về. Vấn đề? JavaScript bundle ngày càng phình to, SEO phụ thuộc vào SSR/SSG workaround, và người dùng phải chờ hai lần: tải JS xong rồi mới fetch data.
Kiến trúc cũ: mọi thứ đều chạy ở client, bundle JS phình to theo thời gian
Next.js 15 với React Server Components (RSC) đảo ngược logic này. Mặc định, mọi component đều là Server Component - nghĩa là render ở server, gửi HTML thuần về browser, không kèm JavaScript. Client Components chỉ được dùng khi thực sự cần interactivity.
Kết quả thực tế? Bundle JavaScript phía client giảm đáng kể, initial page load nhanh hơn, và SEO cải thiện tự nhiên không cần trick gì thêm.
Server Components: dùng khi nào, làm được gì
Rule of thumb đơn giản: nếu component không cần onClick, useState, hoặc browser API - hãy để nó là Server Component.
Server Component phù hợp nhất khi chỉ cần đọc data và render HTML
Server Components có thể:
- Fetch data trực tiếp (kể cả query database) mà không cần API route
- Đọc file system, environment variables bí mật
- Import các thư viện nặng mà không ảnh hưởng bundle client
- Render Markdown, highlight code, xử lý dữ liệu phức tạp
Lưu ý quan trọng: Server Components không thể nhận event handlers, không dùng hooks như useState hay useEffect, và không truy cập browser APIs (window, document, localStorage). Nếu cần những thứ này, bạn phải chuyển sang Client Component.
Một điểm hay của RSC là composability - bạn có thể lồng Client Component bên trong Server Component, nhưng không ngược lại. Pattern này cho phép giữ phần lớn app ở server, chỉ "island" nhỏ nào cần interactivity mới là client.
Client Components: đừng sợ dùng, nhưng biết giới hạn
Có một hiểu nhầm khá phổ biến sau khi Next.js 13 ra mắt RSC: nhiều người nghĩ Client Components là "xấu" và cần tránh tối đa. Thực ra không phải vậy.
Client Components vẫn render phía server ở lần đầu (SSR), nhưng sau đó hydrate ở browser để có interactivity. Bạn cần chúng cho:
- State management (
useState,useReducer) - Side effects (
useEffect,useLayoutEffect) - Event handlers (
onClick,onChange,onSubmit) - Browser APIs (localStorage, window, geolocation)
- Các thư viện animation như Framer Motion
- Real-time updates (WebSocket, Server-Sent Events)
Client Component phù hợp nhất cho interactivity - nhưng hãy giữ nó nhỏ và tập trung
Một pattern mình hay dùng: push 'use client' xuống thấp nhất có thể trong component tree. Ví dụ, trang blog có Server Component lớn fetch và render bài viết, chỉ phần comment box mới là Client Component. Không cần thiết phải chuyển cả trang thành client chỉ vì một cái button.
Data fetching trong Next.js 15: cách tiếp cận mới
Đây là phần thay đổi nhiều nhất so với Next.js cũ. Bỏ hết getServerSideProps, getStaticProps đi - trong App Router, bạn fetch data trực tiếp trong component.
Next.js 15 mở rộng fetch của browser với cơ chế caching riêng. Tuy nhiên, có một thay đổi quan trọng so với Next.js 14: mặc định fetch không cache nữa (opt-out thay vì opt-in).
Dùng Promise.all để fetch song song - tránh waterfall request làm chậm trang
Một điểm mạnh khác: Request Memoization. Nếu nhiều component trong cùng request gọi cùng một URL, Next.js chỉ thực sự fetch một lần. Bạn không cần prop drilling data từ trên xuống - cứ fetch ngay tại component cần dùng.
Streaming và Suspense: load từng phần thay vì chờ tất cả
Bạn có bao giờ vào một trang và nhìn màn hình trắng trong 2-3 giây chờ tất cả data load xong?
Streaming giải quyết đúng vấn đề này. Thay vì block toàn bộ page cho đến khi mọi data ready, Next.js 15 cho phép stream từng phần HTML về browser ngay khi phần đó render xong.
Streaming cho phép hiển thị UI ngay lập tức, thay vì chờ tất cả data về mới render
User thấy gì? Header và profile xuất hiện ngay, orders skeleton rồi thay bằng real data, analytics xuất hiện sau cùng. Tổng thời gian load không thay đổi, nhưng perceived performance cải thiện rõ rệt.
Kết hợp với loading.tsx (file đặc biệt trong App Router) thì bạn có streaming built-in mà không cần code thêm gì:
Partial Prerendering: best of both worlds
Partial Prerendering (PPR) là tính năng thực nghiệm trong Next.js 15, và nó là thứ mình hào hứng nhất trong bản release này.
Vấn đề truyền thống: bạn phải chọn hoặc là static (nhanh, cache được, SEO tốt) hoặc là dynamic (có data mới nhất). PPR xóa bỏ trade-off này.
Với PPR, một trang được prerender static shell ngay lúc build time - bao gồm layout, navigation, bất kỳ thứ gì không phụ thuộc data động. Phần dynamic (personalized content, real-time data) được stream vào sau khi user request.
PPR: shell tĩnh load ngay từ cache, phần động stream vào theo request - không phải chọn một trong hai
Thực tế, trang product trên sẽ: hiển thị tên, mô tả, ảnh sản phẩm từ cache CDN ngay lập tức (TTFB cực thấp), sau đó stream số lượng tồn kho và gợi ý cá nhân hóa vào trong khi user đang đọc.
