Sự chuyển dịch sang Type-Safe Routing
Sau nhiều năm phụ thuộc vào React Router, tôi đã chạm đến giới hạn khi dashboard doanh nghiệp của chúng tôi mở rộng quá 150 route riêng biệt. Chúng tôi liên tục phải xử lý các lỗi crash lúc runtime vì một developer đã thay đổi tên tham số URL trong trang chi tiết nhưng lại bỏ sót lệnh gọi useParams trong một tab lồng nhau. Do TypeScript không thể “nhìn thấy” các URL, trình biên dịch của chúng tôi về cơ bản trở nên vô dụng đối với logic điều hướng.
Sáu tháng trước, chúng tôi đã chuyển đổi hạ tầng cốt lõi sang TanStack Router. Đây không chỉ là việc thay đổi thư viện; đó là bước chuyển mình sang kiến trúc “Type-First” (Ưu tiên kiểu dữ liệu). Kể từ khi di chuyển, nhật ký Sentry cho thấy lỗi liên quan đến điều hướng đã giảm 85%. Bằng cách xử lý URL như dữ liệu có cấu trúc thay vì các chuỗi văn bản tùy ý, chúng tôi đã loại bỏ được các lỗi ‘Page Not Found’ vốn thường xuyên làm phiền các chu kỳ QA.
Trong một môi trường phức tạp, bạn cần nhiều hơn là một bộ chuyển đổi component. Bạn cần một hệ thống có khả năng xác thực tham số tìm kiếm (search parameters), xử lý các layout lồng nhau mà không gây render thừa, và đảm bảo mọi liên kết nội bộ đều được xác minh tại thời điểm build. Hướng dẫn này sẽ đề cập đến cấu hình thực sự hiệu quả trong môi trường production.
Cài đặt và Thiết lập ban đầu
Hãy bắt đầu với các dependency trước. Nếu bạn đang sử dụng Vite—thứ gần như bắt buộc để có vòng lặp phát triển nhanh hiện nay—bạn sẽ cần plugin chuyên dụng để tự động tạo route.
npm install @tanstack/react-router
npm install -D @tanstack/router-vite-plugin zod
Tôi luôn kết hợp thư viện này với zod. Đây là tiêu chuẩn công nghiệp để xác thực schema và xử lý các công việc nặng nhọc trong việc dọn dẹp các chuỗi URL lộn xộn. Để tự động hóa các phần việc nhàm chán, hãy cập nhật vite.config.ts của bạn để bao gồm plugin TanStack Router:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-vite-plugin'
export default defineConfig({
plugins: [
react(),
TanStackRouterVite(),
],
})
Plugin sẽ theo dõi thư mục src/routes của bạn và tạo file routeTree.gen.ts ở chế độ nền. File này chính là “bộ não” của hệ thống, cung cấp khả năng type safety toàn diện giúp thư viện này trở nên mạnh mẽ.
Cấu hình cốt lõi: Layout và File-Based Routing
Đối với các dự án doanh nghiệp, file-based routing là cách duy nhất để giữ mọi thứ ổn định. Nó cung cấp cho bạn một bản đồ trực quan của toàn bộ ứng dụng ngay trong trình quản lý file. Hãy bắt đầu bằng cách tạo thư mục src/routes để chứa logic của bạn.
Root Route (Route gốc)
Mọi ứng dụng đều cần một khung sườn (shell) toàn cục. Hãy tạo src/routes/__root.tsx để bao bọc ứng dụng của bạn. Đây là vị trí lý tưởng cho các thanh điều hướng, các provider thông báo (toast) toàn cục hoặc các ràng buộc layout chính.
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
export const Route = createRootRoute({
component: () => (
<>
<nav className="p-4 flex gap-4 bg-slate-100">
<Link to="/" className="[&.active]:font-bold">Bảng điều khiển</Link>
<Link to="/inventory" className="[&.active]:font-bold">Kho hàng</Link>
</nav>
<hr />
<Outlet />
{process.env.NODE_ENV === 'development' && <TanStackRouterDevtools />}
</>
),
})
Thành phần <Outlet /> đóng vai trò là nơi giữ chỗ để các route con render. Nhờ plugin Vite, thuộc tính to trên các component <Link> của bạn giờ đây có tính năng tự động gợi ý (auto-complete) đầy đủ. Nếu bạn đổi tên một route, trình biên dịch sẽ ngay lập tức gắn cờ mọi liên kết bị hỏng trong toàn bộ codebase.
Quản lý Search Params với Zod
“Tính năng sát thủ” thực sự cho các ứng dụng doanh nghiệp là quản lý tham số tìm kiếm (search parameter). Chúng ta thường xuyên phải xử lý các bộ lọc phức tạp, phân trang và sắp xếp đa lựa chọn thông qua URL. Theo cách truyền thống, bạn sẽ phải phân tích cú pháp các tham số này một cách thủ công từ useSearchParams, việc này rất tẻ nhạt và dễ phát sinh lỗi.
Định nghĩa schema của bạn trong src/routes/inventory.tsx để router xử lý việc xác thực:
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const inventorySearchSchema = z.object({
page: z.number().catch(1),
filter: z.string().optional(),
sortBy: z.enum(['name', 'price', 'date']).catch('date'),
})
export const Route = createFileRoute('/inventory')({
validateSearch: (search) => inventorySearchSchema.parse(search),
component: InventoryComponent,
})
function InventoryComponent() {
const { page, sortBy } = Route.useSearch()
return (
<div className="p-2">
<h3>Quản lý kho hàng</h3>
<p>Trang hiện tại: {page}</p>
<p>Sắp xếp theo: {sortBy}</p>
</div>
)
}
Bằng cách sử dụng validateSearch, bạn tạo ra một “tường lửa” chống lại dữ liệu rác. Nếu người dùng nhập thủ công ?page=not-a-number, giá trị dự phòng .catch(1) sẽ được kích hoạt và cung cấp một giá trị mặc định an toàn. Logic component của bạn sẽ luôn gọn gàng vì useSearch() luôn trả về một object được định kiểu (typed) hoàn hảo.
Xác minh và Tính linh hoạt
Khả năng hiển thị là rất quan trọng khi quản lý hàng trăm route. Các công cụ devtools tích hợp sẵn rất tuyệt vời, cho phép bạn kiểm tra các kết quả khớp (matches) đang hoạt động và trạng thái loader trong thời gian thực. Điều này hiệu quả hơn đáng kể so với việc sử dụng console-log để theo dõi luồng điều hướng.
Xử lý tải dữ liệu
TanStack Router sử dụng mô hình loader để lấy dữ liệu trước khi component render. Điều này giúp quản lý server state hiệu quả và loại bỏ hiệu ứng “loading waterfall” (thác nước tải dữ liệu), nơi trang web tải xong, sau đó vòng xoay spinner hiện ra, rồi cuối cùng dữ liệu mới đến.
export const Route = createFileRoute('/inventory')({
validateSearch: (search) => inventorySearchSchema.parse(search),
loader: ({ search }) => fetchInventoryData(search),
component: InventoryComponent,
errorComponent: ({ error }) => <div>Lỗi khi tải kho hàng: {error.message}</div>,
pendingComponent: () => <div>Đang tải...</div>,
})
Việc định nghĩa errorComponent ở cấp độ route giúp UI của bạn linh hoạt một cách đáng ngạc nhiên. Đây là một phần của chiến lược xử lý lỗi chuyên nghiệp giúp ứng dụng không bị crash hoàn toàn. Nếu API kho hàng bị lỗi, chỉ phần cụ thể đó của trang hiển thị trạng thái lỗi. Phần còn lại của ứng dụng, bao gồm thanh bên và điều hướng, vẫn hoạt động bình thường và có thể tương tác.
Điều hướng Type-Safe
Điều hướng bằng lập trình với hook useNavigate cũng cung cấp mức độ bảo vệ tương tự. Nếu bạn cố gắng điều hướng đến một route không tồn tại hoặc quên một tham số tìm kiếm bắt buộc, TypeScript sẽ chặn quá trình build. Điều này ngăn chặn trải nghiệm “liên kết hỏng” kinh điển cho người dùng của bạn.
const navigate = useNavigate()
const handleUpdateFilter = (newFilter: string) => {
navigate({
to: '/inventory',
search: (prev) => ({ ...prev, filter: newFilter, page: 1 }),
})
}
Kết luận
Chuyển sang TanStack Router là một quyết định “cửa một chiều” (không muốn quay lại). Một khi bạn đã trải nghiệm lỗi trình biên dịch cho một lỗi đánh máy trong URL, việc quay lại routing dựa trên chuỗi văn bản cảm giác như đang viết code mà không có linter. Thiết lập ban đầu có cấu trúc chặt chẽ hơn React Router, nhưng hàng giờ tiết kiệm được từ việc debug các URL sai định dạng khiến nó trở thành người chiến thắng rõ ràng cho các ứng dụng hiệu năng cao và chuyên nghiệp.
Nếu bạn đang bắt đầu tái cấu trúc (refactor), hãy bắt đầu với việc xác thực tham số tìm kiếm. Nó mang lại hiệu quả đầu tư (ROI) tức thì nhất. Khi nhóm của bạn đã quen với cây route được tạo tự động, bạn sẽ thấy mình có thể triển khai tính năng nhanh hơn với sự tự tin rằng logic điều hướng của mình về cơ bản là không thể phá vỡ.

