Tại sao cần giải pháp thống nhất? Khi phát triển song song Next.js (web) và Expo (mobile) trong một monorepo, việc dùng nhiều thư viện state management riêng lẻ (Redux, Context, Recoil…) khiến codebase phân mảnh, khó bảo trì và tăng bundle size. Một giải pháp nhẹ, framework‑agnostic giúp chia sẻ logic business giữa hai nền tảng mà không buộc runtime cụ thể. Kiến trúc đề xuất - Global client state: Zustand store đặt trong package `shared/state`. Vì Zustand chỉ phụ thuộc vào React, nó chạy y hệt trên web và native. - Server state & caching: TanStack Query (React Query) cung cấp fetching, deduping, stale‑while‑revalidate, mutation queue. Cấu hình `QueryClient` chung trong `shared/query`. - Boundary layer: Tạo custom hook `useAppStore` và `useAppQuery` wrapper để abstract implementation details, dễ dàng mock khi test. Cài đặt cơ bản 1. Thêm dependencies vào workspace root: `zustand @tanstack/react-query`. 2. Tạo `shared/state/useAuthStore.ts` với `create<AuthState>()(...)` – lưu token, user profile, hàm `login/logout`. 3. Tạo `shared/query/queryClient.ts` export singleton `queryClient` với `defaultOptions: { queries: { staleTime: 5 * 60 * 1000 } }`. 4. Trong `apps/web/_app.tsx` và `apps/mobile/App.tsx` wrap root bằng `<QueryClientProvider client={queryClient}>`. Chia sẻ logic giữa Web và Mobile - Viết service layer `shared/api/*.ts` dùng `fetch` (web) hoặc `expo-fetch` (native) nhưng expose cùng interface `ApiClient`. - Custom hook `useUserProfile()` gọi `queryClient.fetchQuery(['user'], api.getProfile)` và đồng bộ vào `useAuthStore` khi thành công. - Mutation `useUpdateProfile()` dùng `useMutation` và `onSuccess` cập nhật store local, tự động invalidate query `['user']`. Mẹo tối ưu và lỗi thường gặp - Tránh circular dependency: chỉ import store/query từ `shared`, không import ngược lại từ `apps/*`. - Sử dụng `persist` middleware của Zustand (AsyncStorage trên native, localStorage trên web) để giữ session khi reload. - Cấu hình `retry` và `cacheTime` phù hợp từng platform; mobile thường cần `retry: 3` do mạng không ổn định. - Test: mock `queryClient` với `react-query/mock` và `act` để kiểm tra store updates. Kết luận Kết hợp Zustand cho client state nhẹ nhàng và TanStack Query cho server state mạnh mẽ mang lại một lớp abstraction thống nhất, giảm boilerplate, dễ dàng mở rộng feature mới cho cả web lẫn mobile. Việc tập trung logic vào package `shared` giúp team duy trì một source of truth duy nhất, tăng tốc độ onboarding và giảm bug liên quan đến state sync.