Tốc độ tải trang không chỉ ảnh hưởng đến trải nghiệm người dùng — đó còn là yếu tố xếp hạng trực tiếp trên Google. Một website chậm hơn 1 giây có thể khiến tỷ lệ chuyển đổi giảm tới 7%, và không có framework nào giúp bạn thoát khỏi bài toán hiệu suất nếu kiến trúc dữ liệu và định tuyến được xây dựng thiếu tối ưu.
TanStack Router và TanStack Query là bộ đôi công cụ đang được cộng đồng React ưa chuộng nhờ khả năng kiểm soát luồng dữ liệu chặt chẽ, hỗ trợ type-safe toàn diện và cơ chế caching thông minh giúp giảm thiểu số lần gọi API không cần thiết. Thay vì tải dữ liệu sau khi render, bộ đôi này cho phép prefetch song song ngay từ giai đoạn định tuyến — mang lại cảm giác phản hồi tức thì cho người dùng cuối.
Trong bài viết này, bạn sẽ được hướng dẫn cụ thể cách kết hợp TanStack Router và TanStack Query để loaderData, staleTime, deduplication và route-level prefetching… hoạt động ăn khớp với nhau — từ đó nâng cao điểm Core Web Vitals và cải thiện thứ hạng SEO một cách bền vững.

Demo: WPStart — Headless WordPress Studio
Tổng quan chuyên sâu về Tanstack Router
TanStack Router là một thư viện điều hướng cho các ứng dụng React và Solid, nổi bật với việc hỗ trợ TypeScript suy diễn hoàn toàn và các API quản lý dữ liệu có kiểu an toàn. Điểm mạnh lớn nhất của thư viện là sự kết hợp giữa routing lồng nhau, quản lý trạng thái tìm kiếm (search params) kiểu JSON-first, và một lớp cache nhẹ tích hợp cho tải dữ liệu (loaders) phù hợp với các thư viện cache phía client như TanStack Query hoặc SWR.

Type-safety toàn diện
TanStack Router biết rõ cấu trúc toàn bộ routes trong ứng dụng — path, path params, search params, context và cấu hình kèm theo — từ đó cung cấp inference mất mát bằng 0. Kết quả là: tự động hoàn thành (autocomplete) chính xác, refactor an toàn hơn và điều hướng an toàn kiểu (typesafe navigation). Với cách thiết kế dựa trên generic types, mọi lời gọi navigate hoặc tạo Link đều kiểm tra tại biên dịch, giảm lỗi runtime.
Search params là trạng thái hàng đầu
Thay vì coi search params như chuỗi, TanStack Router xử lý chúng như một state mạnh mẽ: tự động parse/serialize JSON, validate theo schema, kế thừa từ route cha, và truy cập được trong loaders, components và hooks. API cho phép cập nhật qua useSearch, Link, navigate và router.navigate; hỗ trợ parser/serializer tuỳ chỉnh, middleware cho search params và selector mịn để giảm render thừa. Tóm lại: bạn có thể lưu state có thể bookmark/share ngay trên URL một cách an toàn về kiểu.
Tải dữ liệu và caching thân thiện
Router tích hợp một bộ nhớ đệm nhẹ lấy cảm hứng từ TanStack Query, cung cấp mặc định hợp lý cho caching, invalidation và garbage collection. Kết hợp với API lifecycle như beforeLoad, loader, loaderDeps và context, TanStack Router cho phép định nghĩa dependency dữ liệu một cách tuyên bố (declarative), prefetch tự động, và dễ tích hợp với TanStack Query, SWR, Apollo… Nhờ đó giảm request thừa, tránh stale data, và cho phép invalidation tinh vi khi cần.
Inherited route context & SSR/CSR hỗn hợp
Route context có thể được cung cấp ở nhiều mức (router, root, route), đồng thời kế thừa xuống con, hỗ trợ cả synchronous và asynchronous context. Đây là nơi lý tưởng để đặt auth, theme, singletons hoặc các utility dùng chung. Context này vẫn được infer về kiểu, giúp an toàn khi sử dụng. Hỗ trợ SSR, masking route và error boundaries giúp xây app ổn định cho nhiều kịch bản.
File-based + code-based routing linh hoạt
TanStack Router cho phép kết hợp file-based và code-based routing: plugin Vite hoặc CLI sẽ tạo cấu hình route mà bạn vẫn hoàn toàn kiểm soát. Điều này giúp nhanh khi bắt đầu nhưng không lấy mất khả năng tuỳ chỉnh.
Nói ngắn gọn
Nếu bạn cần một router cho ứng dụng hiện đại với TypeScript hoàn toàn suy diễn, quản lý search params mạnh mẽ, tải dữ liệu có cache thông minh và context kế thừa kiểu an toàn, TanStack Router là lựa chọn hàng đầu. Nó không chỉ cung cấp các tính năng cơ bản của router truyền thống mà còn nâng cấp trải nghiệm developer và hiệu năng ứng dụng.
Tổng quan chuyên sâu về Tanstack Query
TanStack Query, trước đây được biết đến với tên React Query, là thư viện hàng đầu để quản lý server state trong các ứng dụng web hiện đại. Thay vì để lập trình viên tự xử lý việc fetch, cache và đồng bộ dữ liệu từ server, TanStack Query cung cấp một giải pháp thống nhất, hiệu quả và dễ mở rộng.

Vì sao TanStack Query ra đời?
Hầu hết các framework web phổ biến không đưa ra cách tiếp cận rõ ràng cho việc làm việc với dữ liệu bất đồng bộ. Điều này khiến nhiều đội ngũ phải tự xây dựng giải pháp riêng hoặc tận dụng các thư viện quản lý state tổng quát như Redux. Tuy nhiên, server state khác hoàn toàn client state:
- Dữ liệu được lưu trữ ở server, không nằm toàn quyền kiểm soát của ứng dụng
- Luôn cần gọi API bất đồng bộ
- Có thể bị thay đổi bởi người dùng khác hoặc hệ thống khác
- Dễ bị lỗi thời nếu không có cơ chế cập nhật
TanStack Query được thiết kế để giải quyết chính những vấn đề này một cách trực diện.
Những vấn đề TanStack Query giải quyết
TanStack Query giúp đơn giản hóa hàng loạt bài toán phức tạp mà hầu hết ứng dụng web đều gặp phải:
- Cache dữ liệu thông minh và tự động
- Gộp nhiều request trùng nhau thành một
- Tự động refetch dữ liệu khi cần
- Xác định khi nào dữ liệu đã lỗi thời
- Phân trang, lazy loading và tối ưu hiệu năng
- Quản lý bộ nhớ và garbage collection
- Chia sẻ cấu trúc dữ liệu để giảm render không cần thiết
Nhờ đó, code trở nên gọn gàng hơn và dễ bảo trì hơn rất nhiều.
Cách TanStack Query hoạt động
Ở mức cơ bản, TanStack Query xoay quanh các khái niệm như QueryClient, queryKey và queryFn. Chỉ với vài dòng code, bạn có thể fetch dữ liệu, theo dõi trạng thái loading, lỗi và dữ liệu trả về mà không cần viết logic phức tạp.
Ví dụ, khi dùng useQuery, thư viện sẽ tự động:
- Gọi API
- Cache kết quả
- Chia sẻ dữ liệu giữa các component
- Refetch khi dữ liệu không còn hợp lệ
Lợi ích thực tế cho ứng dụng
Việc sử dụng TanStack Query mang lại tác động rõ rệt:
- Ứng dụng phản hồi nhanh và mượt hơn
- Giảm lượng code thủ công liên quan đến side-effect
- Dễ mở rộng tính năng mới
- Tiết kiệm băng thông và tài nguyên bộ nhớ
Nói ngắn gọn
TanStack Query không chỉ là thư viện fetch dữ liệu, mà là nền tảng quản lý server state hoàn chỉnh. Nếu bạn đang xây dựng ứng dụng React hoặc các framework frontend hiện đại, TanStack Query là lựa chọn gần như không thể thiếu để đảm bảo hiệu năng, độ ổn định và trải nghiệm người dùng tốt nhất.
Có thể kết hợp dùng chung Tanstack Router và Tanstack Query chung một dự án không?
Có, hoàn toàn có thể và rất nên kết hợp TanStack Router và TanStack Query trong cùng một dự án. Hai thư viện này được thiết kế để hoạt động song song, bổ trợ cho nhau và không gây xung đột.
TanStack Router chịu trách nhiệm quản lý điều hướng, route, loader, search params và trạng thái URL. Trong khi đó, TanStack Query chuyên xử lý dữ liệu bất đồng bộ như gọi API, cache, refetch, đồng bộ trạng thái server. Khi dùng chung, Router lo phần “đi đâu”, còn Query lo phần “lấy dữ liệu gì”.
Ngoài ra, cả hai đều thuộc hệ sinh thái TanStack nên có triết lý thiết kế tương đồng: type-safe, hiệu suất cao, kiểm soát tốt trạng thái. Việc dùng chung giúp code rõ ràng hơn, dễ mở rộng và dễ bảo trì.
Lợi ích chính
- Tự động dehydrate/hydrate trạng thái query giữa server và client
- Streaming dữ liệu: gửi incremental chunks cho client nếu query resolve trong quá trình SSR
- Tránh waterfall fetch bằng cách preload trong loader
- Xử lý redirect được ném ra từ query/mutation
- Có thể wrap bằng QueryClientProvider hoặc dùng provider riêng
Cài đặt Sử dụng package tích hợp riêng:
npm i -D @tanstack/react-router-ssr-query
(hoặc pnpm/yarn/bun tương ứng)
Thiết lập cơ bản
- Tạo một QueryClient mới cho mỗi request trong SSR (rất quan trọng)
- Tích hợp vào router và gọi setupRouterSsrQueryIntegration
Ví dụ file src/router.tsx:
import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { routeTree } from './routeTree.gen'
export function getRouter() {
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
context: { queryClient },
scrollRestoration: true,
defaultPreload: 'intent',
})
setupRouterSsrQueryIntegration({
router,
queryClient,
// handleRedirects: true, // mặc định đã bật
// wrapQueryClient: true, // mặc định sẽ wrap với QueryClientProvider
})
return router
}
Ghi chú:
- Nếu bạn đã tự quản lý QueryClientProvider thì để wrapQueryClient: false.
- Trong SSR cần khởi tạo QueryClient mới cho mỗi request để tránh leak cache.
Tùy chỉnh dehydrate/hydrate
Bạn có thể truyền dehydrateOptions và hydrateOptions:
Ví dụ:
setupRouterSsrQueryIntegration({
router,
queryClient,
dehydrateOptions: {
shouldDehydrateQuery: (query) => query.meta?.ssr !== false,
},
hydrateOptions: {
defaultOptions: {
queries: {
gcTime: 60_000,
},
},
},
})
Các use-case:
- Điều chỉnh gcTime cho queries được hydrate
- Bỏ những query không muốn serialize bằng shouldDehydrateQuery
- Cung cấp serializeData / deserializeData khi cần custom serialization (ví dụ chứa Date, BigInt…)
Hành vi SSR & streaming
- Khi server render: integration sẽ dehydrate state ban đầu và stream những query resolve trong quá trình render.
- Trên client: integration hydrate trạng thái ban đầu, sau đó incremental hydrate các query được stream.
- Lưu ý: chỉ các hook/loader sử dụng useSuspenseQuery hoặc loader prefetches mới tham gia SSR/streaming. useQuery thông thường không chạy trên server.
Sử dụng trong route: useSuspenseQuery vs useQuery
- useSuspenseQuery: chạy trên server trong SSR khi dữ liệu cần thiết, được stream về client khi resolve.
- useQuery: không thực thi trên server; fetch ở client sau khi hydrate. Dùng cho dữ liệu không cần SSR.
Ví dụ:
// Suspense: executes on server and streams
const { data } = useSuspenseQuery(postsQuery)
// Non-suspense: executes only on client
const { data, isLoading } = useQuery(postsQuery)
Preload bằng loader và đọc bằng hook (ví dụ)
- Preload critical data trong route loader để tránh waterfall. Ví dụ src/routes/posts.tsx:
import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
const postsQuery = queryOptions({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then((r) => r.json()),
})
export const Route = createFileRoute('/posts')({
loader: ({ context }) => context.queryClient.ensureQueryData(postsQuery),
component: PostsPage,
})
function PostsPage() {
const { data } = useSuspenseQuery(postsQuery)
return <div>{data.map((p: any) => p.title).join(', ')}</div>
}
Prefetching & streaming
- Nếu trong loader bạn gọi fetchQuery hoặc ensureQueryData mà không await/không return promise, query sẽ bắt đầu trên server và stream về client mà không block toàn bộ SSR. Ví dụ:
export const Route = createFileRoute('/user/$id')({
loader: ({ params, context }) => {
context.queryClient.fetchQuery(userQuery(params.id))
// không await và không return -> sẽ stream
},
})
- Nếu bạn return promise từ loader, server sẽ await trước khi trả HTML (block until finished).
Xử lý redirect
- Nếu query hoặc mutation ném redirect(…), integration sẽ bắt và thực hiện navigation trên client.
- Tùy chọn handleRedirects: false nếu muốn tự handle.
Lưu ý triển khai trong môi trường SSR
- Luôn tạo QueryClient mới cho mỗi request.
- Nếu sử dụng custom QueryClientProvider, đặt wrapQueryClient: false.
- Sử dụng shouldDehydrateQuery để tránh serialize dữ liệu nhạy cảm.
- Cấu hình gcTime để kiểm soát thời gian giữ cache hydrated trên client.
- Kiểm soát serialization khi dữ liệu không raw-JSON.
FAQ (các câu hỏi thường gặp)
Q: useQuery có chạy trên server không?
A: Không, useQuery mặc định không chạy trên server; dùng useSuspenseQuery hoặc loader để tham gia SSR/streaming.
Q: Làm sao tránh leak cache giữa các request SSR?
A: Tạo QueryClient mới cho mỗi request (không dùng singleton).
Q: Muốn tùy biến serializing Date/BigInt thì làm sao?
A: Dùng dehydrateOptions.serializeData và hydrateOptions.defaultOptions.deserializeData để cung cấp hàm serialize/deserialize.
Q: Muốn disable tự động wrap QueryClientProvider?
A: Truyền wrapQueryClient: false trong setupRouterSsrQueryIntegration.
Preloading trong Tanstack Router: Hướng dẫn tổng quan và cấu hình nhanh gọn
Preloading trong TanStack Router giúp tải trước route trước khi người dùng thực sự điều hướng, giảm thời gian chờ và cải thiện trải nghiệm người dùng. Đây là kỹ thuật hữu ích khi bạn biết người dùng có xác suất cao sẽ truy cập các trang tiếp theo — ví dụ danh sách bài viết và trang chi tiết bài viết.
Các chiến lược preloading được hỗ trợ
- Intent: kích hoạt preload khi người dùng hover hoặc touchstart trên thẻ <Link>. Phù hợp với các liên kết người dùng có khả năng nhấn tiếp.
- Viewport: dùng Intersection Observer để preload khi <Link> xuất hiện trong vùng nhìn thấy (in-viewport). Phù hợp cho các liên kết “dưới nếp gấp” hoặc off-screen.
- Render: preload ngay khi <Link> được render trong DOM. Dùng cho các route luôn cần tải sẵn.
Cấu hình mặc định
Bạn có thể bật preloading mặc định cho toàn bộ router:
import { createRouter } from '@tanstack/react-router'
const router = createRouter({
// ...
defaultPreload: 'intent',
})
Hoặc ghi đè bằng prop preload trên từng <Link>.
Delay trước khi preload
Mặc định preloading bắt đầu sau 50ms khi hover hoặc touchstart. Thay đổi bằng defaultPreloadDelay trên router hoặc preloadDelay trên <Link>:
const router = createRouter({
// ...
defaultPreloadDelay: 100,
})
Thời gian giữ preloaded data (caching & stale time)
- Preloaded route matches được lưu tạm trong bộ nhớ. Mặc định bị xóa sau 30 giây (cấu hình bằng defaultPreloadMaxAge).
- Dữ liệu preloaded được coi là “fresh” trong 30 giây theo mặc định. Thay đổi bằng defaultPreloadStaleTime hoặc preloadStaleTime trên từng route:
const router = createRouter({
// ...
defaultPreloadStaleTime: 10_000,
})
// hoặc trên route
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => fetchPost(params.postId),
preloadStaleTime: 10_000,
})
Tích hợp với thư viện ngoài (Ví dụ React Query)
Nếu dùng React Query hoặc thư viện caching khác, nên tắt caching nội bộ của TanStack Router để giao quyền quản lý stale cho thư viện ngoài bằng cách đặt defaultPreloadStaleTime hoặc preloadStaleTime = 0. Khi đó loader luôn được gọi và cache bên ngoài quyết định tính mới.
Preload thủ công
Router cung cấp API để preload thủ công:
- preloadRoute(navigateOptions) trả về Promise khi route được preload hoàn tất.
- loadRouteChunk(route) để tải riêng chunk JS của route.
Ví dụ preload route và chunk:
const router = useRouter()
useEffect(() => {
router.preloadRoute({ to: postRoute, params: { id: 1 } })
// load JS chunks
const postsRoute = router.routesByPath['/posts']
router.loadRouteChunk(postsRoute)
}, [router])
Nói ngắn gọn
Preloading với TanStack Router là công cụ mạnh để giảm thời gian tải và tăng cảm giác phản hồi cho ứng dụng. Chọn chiến lược phù hợp (intent, viewport, render), điều chỉnh delay và stale time, và tích hợp khéo với hệ thống cache ngoài (như React Query) để đạt hiệu quả tối ưu.
Tổng quan về Server-Side Rendering với Tanstack Router
Server-Side Rendering (SSR) giúp cải thiện tốc độ tải trang và SEO bằng cách render nội dung trên server trước khi gửi HTML cho trình duyệt.
SSR là gì và lợi ích chính
- SSR render trang trên server, gửi HTML tĩnh kèm dữ liệu đã serialize, giúp cải thiện First Contentful Paint (FCP) và khả năng lập chỉ mục của công cụ tìm kiếm.
- TanStack Router hỗ trợ cả hai chiến lược SSR: non-streaming (toàn bộ trang trong một lần) và streaming (gửi phần quan trọng trước, tiếp tục stream phần còn lại).
Non-streaming SSR với TanStack Router
- Các thành phần cần thiết: RouterClient (client), RouterServer (server) và một trong hai hàm render server: defaultRenderHandler hoặc renderRouterToString.
- Quy trình:
- Tạo router dùng chung bằng hàm createRouter() xuất ra cả server và client.
- Trên server: sử dụng createRequestHandler để xử lý Request, sau đó gọi defaultRenderHandler hoặc renderRouterToString để render HTML hoàn chỉnh kèm dữ liệu đã dehydrate.
- Trên client: khởi tạo router và hydrate bằng <RouterClient router={router} /> (sử dụng hydrateRoot).
- Lưu ý: RouterServer tự động chuyển sang createMemoryHistory trên server để tránh phụ thuộc window.
Streaming SSR với TanStack Router
- Streaming cho phép gửi nhanh phần quan trọng (critical first paint) rồi stream thêm markup và dữ liệu có độ ưu tiên thấp hơn.
- Sử dụng defaultStreamHandler hoặc renderRouterToStream cùng RouterServer.
- Ưu điểm: cải thiện trải nghiệm người dùng cho trang có dữ liệu chậm hoặc từ bên thứ ba.
Dehydration/Hydration và serialization dữ liệu
- TanStack Router tự động dehydrate/rehydrate dữ liệu loader khi tuân thủ các bước SSR.
- Hệ thống serializer mặc định hỗ trợ undefined, Date, Error, FormData.
- Với kiểu dữ liệu phức tạp (Map, Set, BigInt…), cần custom serializer để đảm bảo an toàn và đúng kiểu.
Cảnh báo & lưu ý
- Các API SSR có thể thay đổi vì chúng chia sẻ logic với TanStack Start — được xem là experimental cho đến khi Start ổn định.
- Khi dùng framework server (Express…), cần chuyển đổi Request/Response sang chuẩn web API nếu muốn dùng createRequestHandler.
Triển khai SSR với TanStack Router giúp tăng tốc độ, cải thiện SEO và trải nghiệm người dùng. Chọn non-streaming hoặc streaming tùy yêu cầu dữ liệu, và đảm bảo serialization chính xác cho dữ liệu phức tạp.
Tìm hiểu thêm về Server Rendering & Hydration với Tanstack Query
Server Rendering (SSR) giúp tạo HTML ban đầu trên server để người dùng thấy nội dung ngay khi tải trang. Kết hợp TanStack Query (trước đây React Query), SSR không chỉ render markup có nội dung mà còn nhúng dữ liệu khởi tạo (initial data) vào markup — từ đó giảm số lần gọi API trên client và cải thiện trải nghiệm đầu tiên.
Ba bước chính khi dùng TanStack Query với SSR
- Prefetch trên server: tạo QueryClient trong loader/getServerSideProps/getStaticProps, gọi queryClient.prefetchQuery hoặc fetchQuery để lấy dữ liệu trước khi render. Dùng Promise.all để song song hóa các truy vấn khi có thể.
- Dehydrate: chuyển trạng thái cache của QueryClient thành dạng tuần tự hóa bằng dehydrate(queryClient) và trả về qua props hoặc body response.
- Hydrate trên client: bọc ứng dụng bằng HydrationBoundary state={dehydratedState} (hoặc truyền vào _app để giảm boilerplate) và khởi tạo QueryClient ở level component bằng React.useState để tránh chia sẻ cache giữa các request.
Lưu ý thiết kế nhanh (initialData) và đầy đủ (hydration)
- initialData dễ áp dụng (truyền dữ liệu như props vào useQuery) nhưng có nhiều hạn chế: phải truyền sâu nếu query nằm ở component con, không cập nhật cache nếu đã tồn tại dữ liệu cũ, và không biết thời điểm dữ liệu được fetch (dataUpdatedAt).
- Giải pháp đầy đủ dùng dehydrate/hydrate khắc phục các vấn đề này và hỗ trợ nhiều route cùng dùng chung dữ liệu được prefetch.
Tương tác với Suspense và dependent queries:
- Có thể dùng useSuspenseQuery nếu luôn prefetch mọi query; nếu quên, có thể gây mismatch giữa server và client.
- Đối với dependent queries (ví dụ cần userId để fetch projects), trên server dùng queryClient.fetchQuery để lấy user, rồi prefetch dựa trên kết quả trước khi dehydrate.
Xử lý lỗi và serialization
- prefetchQuery mặc định không ném lỗi; dehydrate chỉ bao gồm queries thành công. Nếu muốn xử lý lỗi (ví dụ trả 404/500), dùng fetchQuery để nhận lỗi và xử lý phù hợp.
- Dữ liệu phải an toàn khi serialize: các giá trị như Date, Error, Map, Set… không an toàn mặc định với Next/Remix; cân nhắc dùng superjson hoặc thư viện serialize an toàn (serialize-javascript, devalue) khi cần.
Hiệu năng và caveats
- Đặt staleTime hợp lý (ví dụ 60s) để tránh refetch thừa khi hydrate.
- Trên server, queryClient được tạo cho mỗi request có thể gây tiêu tốn bộ nhớ; cân nhắc clear() sau khi dehydrate xong hoặc điều chỉnh gcTime (không đặt bằng 0).
- Với Next.js rewrites cùng getStaticProps, có thể xảy ra “second hydration” làm mất referential equality; cần kiểm tra kỹ khi dùng pattern này.
Kết luận: sử dụng TanStack Query cùng SSR và Hydration giúp cải thiện LCP và trải nghiệm người dùng bằng cách phục vụ markup có nội dung và dữ liệu khởi tạo. Thiết lập đúng QueryClient, prefetch/dehydrate/hydrate là chìa khóa để tránh refetch không cần thiết và đảm bảo nhất quán giữa server và client.
Nói thêm một chút về caching trong Tanstack Query (kết hợp Tanstack Router)
Với các ứng dụng React hiện đại, việc chỉ dựa vào framework là chưa đủ; hiệu suất phụ thuộc lớn vào cách bạn tổ chức định tuyến, luồng dữ liệu và cơ chế caching. TanStack Router và TanStack Query giải quyết bài toán này bằng cách cho phép kiểm soát dữ liệu ngay từ tầng routing, thay vì đợi component render xong mới gọi API.
Cơ chế caching của TanStack Query
TanStack Query hoạt động dựa trên queryKey và vòng đời của cache. Khi một useQuery với key ['todos'] được mount lần đầu, nếu chưa có dữ liệu trong cache, ứng dụng sẽ hiển thị trạng thái loading và gửi request. Sau khi dữ liệu được trả về, nó được lưu vào cache và ngay lập tức bị đánh dấu là stale nếu staleTime là 0.
Khi một component khác dùng cùng queryKey được mount, dữ liệu sẽ được trả về ngay từ cache, giúp giao diện hiển thị tức thì. Đồng thời, TanStack Query vẫn thực hiện refetch ngầm để đảm bảo dữ liệu mới nhất. Nhờ cơ chế deduplication, các query trùng key sẽ chia sẻ trạng thái và kết quả, tránh gọi API dư thừa.
Sau khi không còn component nào sử dụng query, dữ liệu vẫn được giữ lại trong cache cho đến khi hết gcTime (mặc định 5 phút). Nếu trong khoảng thời gian này query được sử dụng lại, dữ liệu cũ sẽ được dùng ngay và refetch chạy song song ở nền.
Kết hợp với TanStack Router để prefetch ở mức route
TanStack Router cho phép prefetch dữ liệu ngay trong loader của route. Khi người dùng hover hoặc chuẩn bị điều hướng, Router có thể kích hoạt loader, gọi TanStack Query để fetch dữ liệu trước khi trang render. Kết quả là khi component hiển thị, dữ liệu đã sẵn sàng trong cache, giảm đáng kể LCP và TTI.
Lợi ích cho Core Web Vitals và SEO
Sự kết hợp này giúp:
- Giảm thời gian chờ nội dung chính hiển thị.
- Hạn chế số request không cần thiết.
- Cải thiện tính nhất quán dữ liệu giữa các trang.
Nếu được cấu hình đúng với staleTime, gcTime và route-level prefetching, TanStack Router và TanStack Query mang lại hiệu suất ổn định, trải nghiệm mượt và nền tảng vững chắc cho SEO dài hạn.
Lợi ích và hạn chế khi build dự án Headless WordPress + với Tanstack Router & Query
Headless WordPress kết hợp TanStack Router & Query là lựa chọn phổ biến cho các dự án web hiện đại cần hiệu năng cao và trải nghiệm người dùng tốt. Tuy nhiên, mô hình này cũng có những điểm cần cân nhắc.
Lợi ích khi triển khai Headless WordPress kết hợp TanStack Router + TanStack Query
- Hiệu năng tải trang & SEO cải thiện: Prefetch dữ liệu ở layer routing (loader) và dehydrate/hydrate của TanStack Query giúp server trả HTML đã có nội dung, giảm LCP/FCP — quan trọng cho xếp hạng Google. Với prefetch song song và deduplication, giảm waterfall fetch và số call API đến WordPress REST/GraphQL.
- Kiểm soát dữ liệu rõ ràng, type-safe: TanStack Router cung cấp inference cho route params và search params; TanStack Query chuẩn hóa queryKey/queryFn. Khi dùng TypeScript, giảm bug runtime, refactor an toàn, autocompletion cho dev.
- UX mượt, cảm giác phản hồi tức thì: Route-level prefetch (intent/viewport/render) + cache thông minh (staleTime, gcTime) giúp trang tiếp theo gần như đã sẵn sàng khi user điều hướng, tăng tỷ lệ chuyển đổi.
- SSR & streaming hỗ trợ: Có thể prefetch trên server, dehydrate state trả về client, hoặc stream incremental queries để giao nội dung quan trọng nhanh hơn — phù hợp cho SEO và người dùng mạng chậm.
- Quản lý cache & tránh request thừa: Deduplication và sharing cache giữa các component/route giảm băng thông, hữu ích nếu WordPress host có giới hạn rate hoặc chi phí API cao.
- Tái sử dụng và tổ chức rõ ràng: Tách presentation (React) và CMS (WordPress) rõ ràng, dễ thay backend sau này; router+query cho luồng dữ liệu có cấu trúc, dễ maintain.
Hạn chế và rủi ro cần lưu ý
- Độ phức tạp triển khai cao hơn: Cấu hình SSR/QueryClient cho mỗi request, custom serializer (Date/BigInt), xử lý redirect/404 từ server, stream — tất cả yêu cầu hiểu sâu để tránh leak cache hoặc mismatch hydration.
- Chi phí SSR và tài nguyên server: Prefetch nhiều query trên server cho mỗi request tốn CPU/memory; cần tạo QueryClient mới cho từng request và điều chỉnh gcTime. Với traffic lớn, cần tối ưu batching, caching phía CDN và edge.
- Vấn đề serialization & data fidelity: WordPress có thể trả dữ liệu phức tạp (dates, nested meta). Cần serializer an toàn (superjson/devalue) để dehydrate/hydrate đúng kiểu; nếu không, sẽ gặp lỗi khi hydrate trên client.
- Complexity khi xử lý realtime / frequently-changing content: Nếu nội dung cập nhật liên tục (comment, live view), staleTime phải cấu hình khôn ngoan — hoặc dùng revalidation/ISR/Realtime layer — tránh content cũ ảnh hưởng UX/SEO.
- Kích thước bundle & code-splitting: Tích hợp router, query, SSR helpers có thể tăng bundle nếu không tách chunk đúng cách; cần sử dụng route-level code-splitting và preload chunks có chọn lọc.
- Trở ngại với authoring workflow: Người không kỹ thuật trong WP admin vẫn làm nội dung, nhưng preview tính năng SSR/route-based prefetch có thể khó tái hiện; cần build preview endpoints hoặc live-preview integration.
- Dependency lock-in & learning curve: TanStack Router/Query có API mạnh nhưng khá mới (phiên bản/behavior có thể thay đổi). Team phải chấp nhận learning curve và cập nhật khi API thay đổi.
Khuyến nghị kỹ thuật ngắn gọn
- Sử dụng route-level loaders để ensureQueryData/fetchQuery cho critical data; dùng useSuspenseQuery cho SSR-critical queries, useQuery cho data non-SSR.
- Tạo QueryClient mới cho từng request SSR; dehydrate trước khi gửi HTML; hydrate trên client.
- Dùng serializer (superjson) nếu WP trả Date/BigInt; cấu hình shouldDehydrateQuery để bỏ data nhạy cảm.
- Cài đặt staleTime/gcTime hợp lý (ví dụ staleTime ~30–60s cho nội dung ít đổi; longer cho content tĩnh) và giảm preloadMaxAge để tránh giữ cache vô ích.
- Kết hợp CDN/edge caching cho HTML + API responses; giảm áp lực backend WordPress.
- Giám sát Core Web Vitals và các request tới WP để điều chỉnh prefetch chiến lược (intent vs viewport vs render).
Kết luận
TanStack Router và TanStack Query khi được kết hợp đúng cách tạo thành bộ công cụ mạnh mẽ để tối ưu hiệu năng, cải thiện Core Web Vitals và hỗ trợ SEO bền vững cho các dự án React/Headless WordPress. Bằng cách chuyển phần lớn trách nhiệm tải dữ liệu lên tầng routing — prefetch trong loader, dùng useSuspenseQuery cho SSR-critical queries, và dehydrate/hydrate QueryClient — bạn giảm đáng kể waterfall fetch, tăng tỷ lệ hiển thị nội dung đầu tiên (FCP/LCP) và giữ trải nghiệm người dùng mượt mà.
Để đạt hiệu quả tối đa cần chú ý: khởi tạo QueryClient mới cho mỗi request SSR để tránh leak cache; cấu hình staleTime và gcTime hợp lý; sử dụng serializer an toàn (ví dụ superjson) nếu dữ liệu chứa Date/BigInt; và chọn chiến lược preloading (intent, viewport, render) phù hợp với hành vi người dùng. Mặt khác, mô hình này tăng độ phức tạp triển khai và yêu cầu quản lý tài nguyên server cẩn trọng — đặc biệt khi prefetch nhiều query trên SSR.
Kết luận: nếu bạn sẵn sàng đầu tư vào kiến trúc và quy trình (QueryClient per request, dehydration/hydration, route-level loaders, và caching strategy kết hợp CDN), TanStack Router + TanStack Query là giải pháp thực tiễn để tối ưu tốc độ, giảm request thừa và nâng cao thứ hạng SEO cho ứng dụng Headless WordPress.








