React / Routing / TypeScript

    TanStack Router i React Router v7

    TanStack Router (type-safe params, file-based), React Router v7 (Remix merger, loaders, actions) i Wouter (1.5KB) — nowoczesne routing w React.

    TanStack
    100% typed
    RR v7
    Remix merge
    Next/Expo
    File-based
    Wouter
    1.5KB

    6 routerów React — porównanie

    TanStack Router, React Router v7/v6, Next.js App Router, Wouter i Expo Router — rozmiar, type-safety, SSR i zastosowanie.

    Router Rozmiar Type-safety SSR Kiedy
    TanStack Router ~15KB 100% (inference) Tak (beta) Type-safe SPA, full-stack TypeScript
    React Router v7 ~50KB Tak (loaderData) Tak (Remix) Framework mode, SSR, Remix migration
    React Router v6 ~50KB Częściowa Manual Istniejące projekty, szeroka adopcja
    Next.js App Router Wbudowany Tak (Route params) Tak (RSC) Next.js apps, RSC, server-first
    Wouter 1.5KB Częściowa Tak (static-location) Małe SPA, Preact, bundle minimalizacja
    Expo Router Wbudowany Tak Tak (web) React Native + Web universal apps

    Często zadawane pytania

    Co to jest TanStack Router i jak różni się od React Router?

    TanStack Router: type-safe routing dla React. 100% TypeScript inferencja. Params, search params — wszystko typed. File-based routing (opcjonalne). Loader data typed. Instalacja: npm install @tanstack/react-router. Porównanie z React Router: React Router v6 — szeroko stosowany, mniej typowany. TanStack Router — full type safety, nowy, szybko rośnie. React Router v7 (Remix merger) — data loading, server rendering. Tworzenie route: import {createRootRoute, createRoute, createRouter} from '@tanstack/react-router'. const rootRoute = createRootRoute({component: RootComponent}). const indexRoute = createRoute({getParentRoute: () => rootRoute, path: '/', component: Index}). const router = createRouter({routeTree: rootRoute.addChildren([indexRoute])}). RouterProvider router={router}. Type-safe params: const productRoute = createRoute({path: '/products/$productId', component: ProductPage}). const {productId} = productRoute.useParams(). productId: string — automatycznie typed. Type-safe search params: const searchRoute = createRoute({validateSearch: (search) => ({page: Number(search.page), filter: String(search.filter || '')}), ...}). const {page, filter} = searchRoute.useSearch(). Loader: loader: async ({params}) => fetchProduct(params.productId). const data = Route.useLoaderData(). Type-inferred z loader return. Link component: Link to='/products/$productId' params={{productId: '123'}} — type error jeśli brak params.

    TanStack Router — file-based routing i code splitting?

    File-based routing: @tanstack/router-vite-plugin lub codegen. routes/ directory. _root.tsx — root layout. index.tsx — /. about.tsx — /about. products/ — /products prefix. products/index.tsx — /products. products/$id.tsx — /products/:id. products/$id/edit.tsx — /products/:id/edit. Generowanie: npx tsr generate. Automatyczne budowanie routeTree. Updating: npx tsr watch — watch mode. Vite plugin: import {TanStackRouterVite} from '@tanstack/router-vite-plugin'. plugins: [TanStackRouterVite()]. Auto-generuje src/routeTree.gen.ts. Lazy loading: const ProductPage = lazy(() => import('./routes/products/$id')). Automatyczne code splitting. Każdy route = osobny chunk. Prefetching: Link preload='intent' — prefetch na hover. Link preload='viewport' — prefetch gdy widoczny. router.preloadRoute({to: '/products/$productId', params: {productId: '1'}}). Pending UI: pendingComponent — skeleton podczas ładowania. pendingMs: 100 — pokaż pending po 100ms. pendingMinMs: 500 — minimalne pokazanie pending. Context: getContext: () => queryClient. Dziel przez wszystkie routes. TanStack Query integration: const queryOptions = queryOptions({queryKey: ['product', id], queryFn: () => fetchProduct(id)}). loader: ({context}) => context.queryClient.ensureQueryData(queryOptions(params.productId)). Typ-safe link: useNavigate hook. navigate({to: '/products/$id', params: {id: '1'}, search: {filter: 'new'}}) — wszystko typed.

    React Router v7 — Remix merger i server-side routing?

    React Router v7 (2024): merger z Remix. React Router + Remix = jeden produkt. Framework mode (Remix-like) + Library mode (RR v6-like). Instalacja frameworka: npx create-react-router@latest. App directory structure: app/root.tsx — root layout. app/routes/ — file-based routes. app/routes/_index.tsx — /. app/routes/products.$id.tsx — /products/:id. Loaders: export async function loader({params}: Route.LoaderArgs) { const product = await getProduct(params.id). return {product} }. export default function ProductPage({loaderData}: Route.ComponentProps) { return div{loaderData.product.name}/div }. Type-safe loaderData — automatyczna inferencja. Actions: export async function action({request}: Route.ActionArgs) { const formData = await request.formData(). await updateProduct(formData). return redirect('/products') }. Form: Form method='post'. useActionData() — typed. Server vs Client Routing: serverLoader / clientLoader. serverAction / clientAction. Partial hydration strategies. Nested routes (Remix style): app/routes/products.tsx — layout. app/routes/products.$id.tsx — child. Outlet w parent. Data sharing przez parent loader. Vite plugin: @react-router/dev/vite. vitePlugin() w vite.config.ts. SSR automatyczny. Pre-rendering: Static pre-render dla SEO. prerender: ['/about', '/products']. Migracja z RR v6: backward compatible. Stopniowa migracja. createBrowserRouter — nadal działa.

    Wouter i inne lekkie routery — kiedy nie potrzebujesz React Router?

    Wouter: mały router dla React i Preact. 1.5KB gzip. Hooks-based API. Nie potrzebujesz React Router dla małych apps. import {Route, Switch, Link, useLocation} from 'wouter'. Route path='/about' component={About}. Route path='/user/:id' children={({params}) => User id={params.id}}. useLocation: const [location, setLocation] = useLocation(). setLocation('/about') — navigate. useRoute: const [match, params] = useRoute('/user/:id'). Podobny do React Router ale mniejszy. Wouter vs React Router: Wouter — 1.5KB, hooks. React Router — 50KB, więcej features. Routing bez biblioteki (dla małych SPA): window.history.pushState. window.addEventListener('popstate'). useEffect na location. Bardzo mały projekt — może wystarczyć. Next.js App Router: wbudowany file-based router. Nie potrzebujesz zewnętrznej biblioteki. Folder = route. page.tsx = strona. layout.tsx = layout. (group) — logiczne grupowanie bez URL. @parallel — parallel routes. (...intercept) — route interception. Expo Router: file-based dla React Native. Oparty na Expo i React Navigation. Universal routing (iOS, Android, Web). next/navigation vs react-router: useRouter, usePathname, useSearchParams. Nie kompatybilne między sobą. Router agnostic: jeśli budujesz komponent library — nie używaj konkretnego routera. useHref, useNavigate — inject przez props. Kiedy TanStack Router: type-safety priorytetem. Duże app TypeScript. Wiele params/search params. Kiedy React Router v7: framework features (SSR, loaders). Remix migracja.

    Type-safe navigation — jak TypeScript pomaga w routing?

    Problem bez type-safety: navigate('/prodcuts/123') — literówka. Params nie sprawdzane. Search params dowolne. Brak inferencji z loaderów. TanStack Router rozwiązanie: pełna inferencja. Link to='/products/$productId' params={{productId: '123'}}. TypeScript błąd: brak params. TypeScript błąd: zły klucz. RouteTree generuje typy dla wszystkich routes. Literal types dla paths: declare module '@tanstack/react-router' { interface Register { router: typeof router } }. Teraz: paths auto-complete w IDE. Typed params: productRoute.useParams() — {productId: string}. search params: searchRoute.useSearch() — {page: number, filter: string}. React Router v7 types: Route.LoaderArgs, Route.ComponentProps. Automatyczna inferencja z loaderData. export type loader = typeof loader. export default function Component({loaderData}: Route.ComponentProps). TypeScript + Link: To type parameter. Params type parameter. Search type parameter. Brak undefined keys. nuqs (URL state management): npm install nuqs. useQueryState('page', parseAsInteger.withDefault(1)). URL jako source of truth. Type-safe URL state. Brak custom serialization. React Router + Zod search params: const searchSchema = z.object({page: z.number().default(1)}). const search = searchSchema.parse(Object.fromEntries(searchParams)). Validacja + type-safety. Remix approach: Zod w loader. throw new Response (lub data()) jeśli invalid. TypeScript happy path. Type-safe form handling: useActionData() + Zod. Koniec z form error guessing.

    Kontakt

    Skontaktuj się z nami

    Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.

    Wyślij zapytanie

    Bezpłatna wycena w 24h
    Bez zobowiązań
    Indywidualne podejście
    Ekspresowa realizacja

    Telefon

    +48 790 814 814

    Pon-Pt: 9:00 - 18:00

    Email

    adam@fotz.pl

    Odpowiadamy w ciągu 24h

    Adres

    Plac Wolności 16

    61-739 Poznań

    Godziny pracy

    Pon - Pt9:00 - 18:00
    Sob - NdzZamknięte

    Wolisz porozmawiać?

    Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.

    Zadzwoń teraz