Next.js 15 và React 19: khi nào nên dùng Server Components, Server Actions và render phía server

3 tháng trước · 10 phút đọc
Server Components là gì và tại sao React 19 làm nó quan trọng hơn
Trước React 18, mọi component đều chạy trên browser. Bạn load trang, JavaScript tải xuống, React khởi động, component render, rồi data mới được fetch. Chuỗi này tốn thời gian, và bundle size phình to theo từng dependency bạn thêm vào.
React 19 ổn định hóa Server Components sau nhiều năm thử nghiệm. Next.js 15 xây dựng hoàn toàn trên nền này. Điểm khác biệt cốt lõi: Server Components chạy trên server, không bao giờ xuất hiện trong JavaScript bundle gửi về browser.
Server render HTML sẵn - browser nhận trang đã có nội dung
Điều này có nghĩa gì trong thực tế? Giả sử bạn có một blog dùng marked để render Markdown. Với Client Component, toàn bộ thư viện marked (~50KB) được gửi xuống browser. Với Server Component, marked chạy trên server, browser chỉ nhận HTML đã render sẵn. Bundle size về 0 cho dependency đó.
Next.js 15 mặc định mọi component trong thư mục app/ đều là Server Component. Bạn phải khai báo rõ 'use client' ở đầu file nếu muốn component đó chạy trên browser.
Mình nhớ lần đầu chuyển một project từ Pages Router sang App Router, cảm giác lạ nhất là fetch data thẳng trong component mà không cần useEffect hay getServerSideProps. Nó đơn giản đến mức khó tin là đúng.
Khi nào dùng Server Components, khi nào dùng Client Components
Câu hỏi này gây nhầm lẫn nhiều nhất khi mới bắt đầu với App Router. Không có công thức cứng nhắc, nhưng có một mental model đơn giản: nếu component cần tương tác hoặc state, dùng Client. Còn lại, mặc định Server.
Chọn đúng loại component từ đầu tiết kiệm nhiều refactor về sau
Cụ thể hơn:
Dùng Server Component khi:
- Fetch data từ database hoặc API (không cần loading state phức tạp)
- Render nội dung tĩnh hoặc bán tĩnh (blog, landing page, product listing)
- Dùng các thư viện nặng chỉ để xử lý data (markdown parser, date formatter)
- Cần truy cập server-only resources (file system, secrets, internal APIs)
Dùng Client Component khi:
- Dùng
useState,useEffect, hoặc bất kỳ hook nào - Cần event listeners (onClick, onChange, onSubmit)
- Dùng browser APIs (localStorage, geolocation, window)
- Cần real-time updates hoặc WebSocket
Một pattern hay trong thực tế là composition: giữ Server Component bao ngoài, nhúng Client Component nhỏ bên trong đúng chỗ cần.
Tư duy đúng: đẩy 'use client' xuống thấp nhất có thể trong cây component. Càng ít component phải chạy trên browser, bundle size càng nhỏ, First Load JS càng thấp.
Server Actions: submit form và mutate data không cần API endpoint
Đây là phần mình thấy thú vị nhất trong Next.js 15. Server Actions cho phép bạn định nghĩa một hàm chạy trên server, rồi gọi nó thẳng từ component như một hàm bình thường, kể cả từ Client Component.
Nghe có vẻ lạ, nhưng đây là cách nó hoạt động: Next.js tự tạo một API endpoint ẩn, serialize arguments, gửi request, rồi trả kết quả về. Bạn không cần viết /api/submit-form nữa.
Server Action loại bỏ hẳn lớp API route cho các mutation đơn giản
useActionState là hook mới trong React 19, thay thế useFormState cũ. Nó handle loading state, error state và kết quả trả về từ Server Action trong một hook duy nhất.
Lợi ích rõ nhất: giảm boilerplate đáng kể. Không cần viết fetch trong client, không cần tạo API route riêng, không cần quản lý loading state thủ công. Và vì logic chạy trên server, bạn có thể truy cập DB trực tiếp, không cần lo lộ credentials.
Một điểm quan trọng cần nhớ: Server Actions vẫn là HTTP request thật. Progressive enhancement cũng hoạt động - nếu JavaScript bị disabled, form HTML thuần vẫn submit được vì action của form trỏ đến endpoint thật.
Static, Dynamic và Streaming: 3 kiểu render trong Next.js 15
Next.js 15 không chỉ có một kiểu render. Hiểu được 3 kiểu này giúp bạn tối ưu đúng chỗ thay vì áp dụng một công thức cho tất cả.
Static rendering (mặc định): Page được render lúc build time hoặc sau khi revalidate. Phù hợp cho nội dung ít thay đổi như blog, trang marketing, documentation. Next.js 15 tích cực cache hơn Next.js 14 - mặc định fetch không cache nữa (breaking change đáng chú ý).
Dynamic rendering: Page render mỗi request. Tự động kích hoạt khi bạn dùng cookies(), headers(), hay searchParams. Phù hợp cho dashboard, trang cá nhân hóa, giỏ hàng.
Streaming: Gửi HTML từng phần về browser trong khi server vẫn đang xử lý. Dùng <Suspense> để bao quanh component chậm.
Streaming giúp người dùng thấy nội dung sớm thay vì chờ toàn trang xong
Với cách trên, browser nhận ngay phần header và nav. RecentOrders xong trước thì stream trước (800ms), RevenueChart stream sau (1.2s). Nếu không dùng Streaming, cả trang phải chờ 1.2s mới có gì để hiển thị.
Breaking change quan trọng trong Next.js 15: fetch requests không còn cached mặc định. Nếu bạn muốn cache, phải khai báo rõ:
Nếu bạn đang nâng cấp từ Next.js 14, đây là thứ dễ break nhất. Kiểm tra lại toàn bộ fetch calls trước khi deploy.
Trade-off thực tế và khi nào không nên dùng Server Components
Server Components không phải silver bullet. Có những tình huống bạn nên cân nhắc kỹ trước khi dùng.
Debugging phức tạp hơn: Lỗi xảy ra trên server, log xuất hiện ở terminal, không phải DevTools của browser. Khi component vừa có phần server vừa có phần client, trace lỗi đòi hỏi quen hơn với mental model mới.
Không phù hợp với real-time data: Nếu bạn build chat app, live dashboard cập nhật mỗi giây, hay bất cứ thứ gì cần WebSocket - Server Components không giúp được. Bạn vẫn cần Client Component với SWR, React Query, hoặc WebSocket.
Vendor lock-in một phần: App Router của Next.js implement Server Components theo cách riêng. Migrate sang framework khác sau này sẽ tốn công hơn so với dùng Pages Router thuần.
Chọn đúng tool cho đúng use case, không phải cứ mới là dùng
Khi nào vẫn nên dùng Pages Router (hoặc Vite + React):
- Dự án nhỏ, team chưa quen App Router
- App có nhiều real-time features từ đầu
- Cần migrate nhanh, không có thời gian học mental model mới
- SPA thuần không cần SEO (internal tools, admin panel)
Mình thấy một pattern phổ biến là over-engineering: chuyển toàn bộ codebase sang App Router rồi nhận ra 80% components vẫn phải là Client Components vì logic business phức tạp. Kết quả? Code khó đọc hơn, không benefit gì thêm.
Quy tắc thực tế: nếu page của bạn cần SEO và data phần lớn là static hoặc server-fetched, Server Components sẽ giúp nhiều. Nếu app là SPA nặng với nhiều interaction, lợi ích giảm đi đáng kể.
Nếu bạn muốn nắm vững React trước khi bước vào Next.js 15, khóa Xây Dựng Website với ReactJS trên F8 cover từ cơ bản đến nâng cao - đủ nền để không bị ngợp khi gặp App Router lần đầu.
Tổng kết và bước tiếp theo
Ba thứ cần nhớ từ bài này:
- Server Components giảm bundle size và đơn giản hóa data fetching - dùng mặc định, chỉ opt-in
'use client'khi thật sự cần. - Server Actions loại bỏ API routes cho các mutation đơn giản - phù hợp cho forms, CRUD operations, không phù hợp cho logic business phức tạp cần nhiều lớp validation.
- Streaming với Suspense cải thiện perceived performance rõ nhất trên các page có nhiều data sources chậm độc lập nhau.
Next.js 15 và React 19 không thay đổi cách bạn build UI - chúng thay đổi ở đâu logic chạy. Mental model này mất vài ngày để quen, nhưng khi quen rồi thì code sạch và nhanh hơn đáng kể.
Bước tiếp theo mình gợi ý:
- Tạo một project App Router mới, thử fetch data thẳng trong Server Component xem cảm giác thế nào
- Viết một Server Action đơn giản cho form contact hoặc comment
- Thêm
<Suspense>vào một component chậm và so sánh LCP trước/sau
Nếu bạn đang bắt đầu từ đầu với React, nền tảng vững thì việc tiếp cận Next.js 15 sẽ dễ hơn nhiều. Khóa Xây Dựng Website với ReactJS của F8 là điểm khởi đầu tốt - miễn phí, có bài tập thực hành.
Gặp vướng mắc chỗ nào, drop comment mình xem cùng.
Bắt đầu nhỏ, hiểu rõ từng phần trước khi migrate cả project
