Next.js & ReactNổi bật

20/06/2026 · 7 phút đọc

Cách dùng URL Params, TanStack Query, Zustand và React Local State trong Next.js

Trong kiến trúc Server-first, quản lý state không còn là gom tất cả vào một kho lưu trữ toàn cục. Đó là nghệ thuật đặt dữ liệu đúng chỗ để tối ưu hóa hiệu năng render và biệt lập hóa phạm vi ảnh hưởng.

FrontendNext.jsState ManagementArchitecture

Cuộc khủng hoảng mang tên "Toàn cầu hóa trạng thái"

Trong kỷ nguyên của các Single Page Application (SPA) truyền thống, các thư viện quản lý trạng thái toàn cục (Global State Management) như Redux từng được coi là "đấng cứu thế". Thói quen của đa số kỹ sư là tiện tay ném tất cả mọi thứ — từ dữ liệu danh sách trả về từ API, thông tin người dùng, cho đến trạng thái đóng/mở của một cái Sidebar — vào một Kho lưu trữ tập trung (Centralized Store).

Khi chuyển dịch sang kiến trúc Next.js App Router (React 19), tư duy này lập tức trở thành một quả bom nổ chậm về mặt hiệu năng. Việc lạm dụng Global Store ở tầng Client tạo ra các hệ lụy nghiêm trọng:

  • Phá vỡ kiến trúc Server-first: Ép các Server Component phải chuyển đổi thành Client Component vô điều kiện chỉ để tiêu thụ một mẩu dữ liệu nhỏ từ store.
  • Over-rendering diện rộng: Khi cấu trúc dữ liệu trong Global Store thay đổi, hàng loạt component không liên quan bị ép phải re-render ngoài ý muốn nếu không được tối ưu hóa selector cực kỳ ngặt nghèo.
  • Làm nhiễu loạn AI Agent: Khi không có ranh giới dữ liệu rõ ràng, AI Coding Agent sẽ rất khó phán đoán dòng chảy của dữ liệu (Data Flow), dẫn đến việc sinh code sai lệch logic nghiệp vụ.

Một chiến lược quản lý State thực dụng trong Next.js đòi hỏi chúng ta phải áp dụng Nguyên lý bất biến về vị trí trạng thái (State Locality): Đặt dữ liệu ở cấp độ thấp nhất có thể trong cây component, sát nhất với nơi nó được tiêu thụ.


Bản đồ phân định 4 vùng lãnh thổ của State

Thay vì dùng một công cụ duy nhất, một hệ thống Frontend chuẩn production sẽ chia trạng thái thành 4 loại biệt lập dựa trên Nguồn gốc dữ liệu (Data Origin)Vòng đời (Lifecycle) của chúng.

Rendering Mermaid diagram...

Tầng 1: Server State & URL Params (Mặc định cho dữ liệu hiển thị)

Trong Next.js App Router, URL chính là "Kho lưu trữ trạng thái" tối thượng, đáng tin cậy và có khả năng chia sẻ (Shareable Single Source of Truth) tốt nhất mà đa số lập trình viên thường bỏ quên.

  • Bản chất dữ liệu: Dữ liệu danh sách bài viết, bộ lọc tìm kiếm (Search), tham số phân trang (page=2), bộ lọc danh mục (category=tech).

  • Vị trí lưu trữ: Trực tiếp trên thanh địa chỉ của trình duyệt dưới dạng Query Strings (?search=react&sort=asc).

  • Tại sao nên dùng: * Giúp trang web hoàn toàn có thể bookmark hoặc chia sẻ liên kết (Shareable URL). Người dùng gửi link cho bạn bè và họ sẽ nhìn thấy chính xác những gì người gửi đang thấy.

  • Tận dụng tối đa sức mạnh của Server Component: Mỗi khi URL thay đổi, Next.js sẽ kích hoạt lại hàm fetch dữ liệu trên Server mà không cần một dòng code Client-side fetching nào.

  • Cách thực thi: Tiêu thụ searchParams trực tiếp tại tệp page.tsx (Server) hoặc sử dụng hook useSearchParams từ next/navigation (Client).


Tầng 2: Client Async Cache — TanStack Query (Khi cần tương tác động)

Khi ứng dụng Frontend cần các tương tác bất đồng bộ phức tạp vượt ra ngoài khả năng hiển thị tĩnh, đó là lúc Server Component nhường sân khấu cho TanStack Query (v5).

  • Bản chất dữ liệu: Dữ liệu có tính biến động cao, cần đồng bộ liên tục hoặc có các tương tác thay đổi trạng thái ngay lập tức trên UI (Mutations), ví dụ: Giỏ hàng (Cart), danh sách thông báo realtime, trạng thái bấm Like/Bookmark bài viết, dữ liệu biểu đồ tài chính cần Polling liên tục mỗi 5 giây.

  • Vị trí lưu trữ: Bộ nhớ đệm Client-side cache của TanStack Query.

  • Tại sao nên dùng: Quản lý tự động các trạng thái phức tạp của tác vụ mạng: isLoading, isFetching, isError, staleTime.

  • Hỗ trợ cơ chế Optimistic Updates (Cập nhật giao diện trước khi API phản hồi thành công) — mang lại trải nghiệm mượt mà tối đa cho người dùng.

  • Quy tắc nghiêm ngặt: Chỉ kích hoạt TanStack Query khi và chỉ khi có nhu cầu Client caching hoặc tương tác động. Tuyệt đối không dùng để fetch dữ liệu cho các trang thuần đọc (Read-only pages).


Tầng 3: Global UI Store — Zustand (Chỉ dành cho cấu hình hệ thống)

Chúng ta loại bỏ Redux/Context quá khổ và thay thế bằng Zustand — một thư viện quản lý state cực kỳ nhẹ, không gây hiện tượng zombie render và hoạt động theo cơ chế pub-sub tách biệt khỏi vòng đời của React.

  • Bản chất dữ liệu: Các trạng thái UI thuần túy có phạm vi ảnh hưởng xuyên suốt toàn bộ ứng dụng (Cross-feature UI states).
  • Vị trí lưu trữ: Một Zustand store tập trung nằm tại src/stores/app-store.ts.
  • Tập dữ liệu cho phép: Trạng thái đóng/mở của Sidebar toàn cục, cấu hình giao diện (Dark/Light/System Mode), thông tin phiên đăng nhập tối giản của User (User Session metadata để hiển thị Avatar trên Header).
  • Quy tắc nghiêm ngặt: Không bao giờ lưu trữ dữ liệu trả về từ API vào Zustand. Nếu bạn thấy mình đang viết một hàm fetchUserData bên trong một hành động (action) của Zustand, bạn đã đi sai kiến trúc. Dữ liệu đó thuộc về TanStack Query hoặc Server Component.

Tầng 4: Local UI State — useState / useReducer (Cô lập triệt để)

Vũ khí nguyên bản và mạnh mẽ nhất của React chính là các hook nội bộ của chính component đó.

  • Bản chất dữ liệu: Trạng thái UI mang tính tạm thời và cục bộ.
  • Vị trí lưu trữ: Ngay bên trong tệp tin .tsx của Component hoặc Feature cụ thể đó.
  • Tập dữ liệu cho phép: Trạng thái đóng/mở của một Modal cụ thể, dữ liệu thô người dùng đang gõ vào một Form trước khi nhấn nút Submit, trạng thái hover, trạng thái active của một Tab nội bộ.
  • Tại sao nên dùng: Đảm bảo tính đóng gói (Encapsulation). Khi Component này Unmount, toàn bộ bộ nhớ của trạng thái đó được trình duyệt giải phóng hoàn toàn, giữ cho ứng dụng luôn sạch sẽ và không rò rỉ bộ nhớ.

Bảng đối chiếu quyết định kiến trúc nhanh

Trước khi đặt bút viết một dòng code tạo State, hãy ép bản thân hoặc AI Agent đi qua bảng khảo sát này:

Câu hỏi kiểm traVị trí chính xác của StateCông nghệ áp dụng
Dữ liệu này có cần Google Index (SEO) hoặc có cần gửi link cho người khác xem chính xác kết quả không?URL Params / Server ComponentsearchParams / page.tsx
Dữ liệu này có cần tự động refetch sau một khoảng thời gian, hoặc cần tính năng bấm nút "Lưu" hiện kết quả ngay lập tức không?Client Async CacheTanStack Query v5
Trạng thái này có ảnh hưởng đến giao diện của nhiều trang hoàn toàn khác nhau (ví dụ: Sidebar co giãn) không?Global UI StoreZustand
Trạng thái này nếu mất đi khi người dùng chuyển trang hoặc F5 thì có làm sao không?Local UI StateuseState / useReducer

Kết luận: Kỷ luật kiến trúc mang lại sự tự do

Một hệ thống Frontend bền vững là một hệ thống không có sự nhập nhằng về mặt dữ liệu. Bạn không bao giờ được phép nhân bản cùng một trạng thái nằm ở cả 3 nơi: vừa nằm trên URL, vừa nhét vào Zustand, lại vừa lưu trong Cache của Query.

Bằng cách thiết lập một chiến lược phân định ranh giới State rõ ràng, bạn đã tạo ra một Codebase có tính dự đoán cực cao. Hiệu năng ứng dụng sẽ tăng vọt vì số lượng Client Component được giảm thiểu tối đa, đồng thời tạo ra một môi trường "sạch" giúp các kỹ sư và AI Agent đồng thực thi mà không dẫm chân lên nhau.

[ Bài liên quan ]

Next.js & ReactNổi bật

20/06/2026 · 6 phút đọc

Kiến trúc Server-first và Luồng Xử lý Dữ liệu Nội bộ trong Next.js App Route

Thiết kế hệ thống không chỉ là viết mã nguồn sạch, mà còn là tối ưu hóa tài nguyên mạng. Khám phá cách xây dựng dịch vụ xử lý Markdown bền vững, loại bỏ overhead từ API nội bộ và quản lý cấu trúc dữ liệu nhất quán.

Next.jsReact Server ComponentsSystem DesignPerformance
Xem tất cả
Next.js & ReactNổi bật

20/06/2026 · 6 phút đọc

Ownership Rules trong Next.js: Ranh giới tuyệt đối giữa App, Features, Components và Lib

Khi dự án Frontend production lớn dần, sai lầm phổ biến nhất là đặt code sai chỗ. Thiết lập ranh giới sở hữu rõ ràng giúp mã nguồn dễ dự đoán đối với con người và an toàn khi cộng tác cùng AI Agent.

Next.jsArchitectureSystem DesignSoftware Engineering
Xem tất cả