React Router 7
Framework Mode (Remix v3), file-based routing, loaders, actions, typegen i SSR — vs Next.js i TanStack.
Porównanie frameworków React — rendering i routing
React Router 7, Next.js, TanStack Start, Astro i SvelteKit — model renderowania i kiedy wybrać.
| Framework | Rendering | Routing | Kiedy |
|---|---|---|---|
| React Router 7 Framework | SSR + SPA + Streaming | File-based + config | Remix użytkownicy, Vite, multi-host |
| React Router 7 Library | Client-side | Manual createBrowserRouter | RR6 upgrade, istniejące projekty |
| Next.js 15 | RSC + SSR + Static | File-based App Router | Produkcja, Vercel, największy ekosystem |
| TanStack Start | SSR + SPA | Type-safe file-based | Max type safety, eksperymentalne |
| Astro 5 | MPA + Islands + SSR | File-based pages | Content sites, blog, dokumentacja |
| SvelteKit | SSR + SPA + Static | File-based | Svelte, mały bundle, szybki DX |
Często zadawane pytania
React Router 7 — co nowego i jak się różni od Remiksa?
React Router 7: scalenie z Remix. Remix v3 = React Router v7. Michael Jackson i Ryan Florence. Dwa tryby: Framework Mode (pełny, jak Remix) i Library Mode (jak RR6). Framework Mode: plik-based routing. Loaders i actions. SSR + SPA. Vite plugin. Library Mode: klasyczny React Router. useRoutes, createBrowserRouter. Bez file-based. Bez SSR automatycznego. Migracja Remix -> RR7: praktycznie 1:1 API. Adapter pattern: Vercel, Cloudflare, Netlify. Node.js wbudowany. Instalacja Framework Mode: npx create-react-router@latest. Vite plugin: plugins: [reactRouter()]. Routes: app/routes/home.tsx. app/routes/blog.$slug.tsx. Plik = route. Loader: export async function loader({params}: Route.LoaderArgs) { return json(await getData(params.slug)) }. Action: export async function action({request}: Route.ActionArgs) { const data = await request.formData(). return redirect('/') }. Component: export default function Home({loaderData}: Route.ComponentProps) { return div {loaderData.title} /div }. Type safety: Route.LoaderArgs, Route.ComponentProps. Typegen automatyczny. Różnice RR7 vs Remix v2: nazwy pakietów: react-router zamiast @remix-run/react. Adaptery osobne. Vite zamiast Esbuild. useLoaderData() -> loaderData prop. clientLoader: client-side loader. Oba: loaders, actions, nested routes, defer.
React Router 7 — file-based routing i nested routes?
File-based routing w RR7: app/routes/ katalog. Każdy plik = route segment. Konwencje: home.tsx = /home. blog._index.tsx = /blog (index). blog.$slug.tsx = /blog/:slug. blog.tsx = layout route dla /blog/*. _auth.tsx = layout bez URL (pathless). _auth.login.tsx = /login (z _auth layout). Zagnieżdżanie: blog.tsx (layout) + blog.$slug.tsx (child). Outlet wymagany w layout. Pathless layouts: _marketing.tsx + _marketing.about.tsx = /about z marketing layout. Bez _marketing w URL. Splats: $.tsx = catch-all /*. Params: blog.$category.$slug.tsx = /blog/:category/:slug. Opcjonalne: blog.($slug).tsx. Route groups: (group)/page.tsx — group ignorowany w URL. Nested layouts: app/routes/dashboard.tsx (layout). app/routes/dashboard.analytics.tsx (child). dashboard/analytics w URL. Linki: import {Link, NavLink} from 'react-router'. NavLink: activeClassName lub isActive prop. Programmatic: import {useNavigate} from 'react-router'. const navigate = useNavigate(). navigate('/home'). navigate(-1) — back. Route config: routes.ts alternatywa: import {route, layout, index} from '@react-router/dev/routes'. export default [layout('./layouts/main.tsx', [index('./home.tsx'), route('/about', './about.tsx')])] as RouteConfig. Manual bez file-based.
React Router 7 — loaders, actions i typegen?
Loaders: async getData na server (lub client). Przed renderowaniem. Bez useEffect. Server loader: export async function loader({params, request}: Route.LoaderArgs) { const user = await db.user.findUnique({where: {id: params.id}}). return {user} }. Client loader: export async function clientLoader({serverLoader}: Route.ClientLoaderArgs) { const cached = cache.get('data'). if (cached) return cached. return await serverLoader() }. clientLoader.hydrate = true — run on hydration. Action: POST/PUT/DELETE handler. export async function action({request}: Route.ActionArgs) { const form = await request.formData(). const email = form.get('email') as string. await sendEmail(email). return {success: true} }. Form submit: import {Form} from 'react-router'. Form method='post' action='/subscribe' input name='email' /. Typegen: .react-router/types/ generowane automatycznie. Każdy plik route = własne typy. Route.LoaderArgs — params typed. Route.ComponentProps.loaderData — typed data. Konfig: react-router.config.ts: export default {ssr: true} as Config. SPA mode: ssr: false. Prerendering: prerender: ['/about', '/blog']. Build time HTML. Defer: import {defer, Await} from 'react-router'. defer({slow: slowPromise}). Suspense Await fallback={Loading /} resolve={data.slow}. Streaming SSR.
React Router 7 vs Next.js vs TanStack Start — kiedy wybrać?
React Router 7 (Framework): Remix-like. Vite-based. Loaders+Actions. Server adapters. Kiedy: znasz Remix. Chcesz mniej opinii niż Next.js. Loaders zamiast RSC. TanStack Start: TanStack Router + SSR. Type-safe routing. Server functions. Vinxi. Alpha 2024. Kiedy: max type safety. Eksperymentalne projekty. Next.js App Router: RSC, Server Actions. Vercel-optimized. Największy ekosystem. Kiedy: produkcja, maksymalne wsparcie. SvelteKit: Svelte framework. Adapters. Superb DX. Kiedy: preferujesz Svelte. Astro: content-first. Islands. MPA + SPA hybrid. Kiedy: blog, dokumentacja. React Router 7 SPA Mode: ssr: false. Tylko client-side. Jak CRA. Kiedy: SPA bez SSR, migracja CRA. Library Mode (bez Framework): tylko routing. Bez loaders. Jak RR6. Kiedy: istniejące projekty, Webpack, własny SSR. Porównanie renderowania: Next.js RSC: React Server Components. RR7: traditional SSR + loaders. TanStack: RPC-style server fns. Decyzja 2024: Next.js 15 — produkcja, Vercel. React Router 7 — Remix-like, Vite, multi-host. TanStack — eksperymentalny, type-safe. Astro — content sites. SvelteKit — Svelte fans.
React Router 7 — błędy, pending states i optymistyczny UI?
Error boundaries: export function ErrorBoundary({error}: Route.ErrorBoundaryProps) { if (isRouteErrorResponse(error)) { return div Status: {error.status} {error.data} /div }. return div Unknown error /div }. isRouteErrorResponse: sprawdź typ błędu. 404: throw new Response('Not Found', {status: 404}). Pending state: import {useNavigation} from 'react-router'. const {state} = useNavigation(). state: 'idle' | 'loading' | 'submitting'. Loading skeleton gdy state === 'loading'. Form pending: const navigation = useNavigation(). const isSubmitting = navigation.state === 'submitting'. Optimistic UI: import {useOptimistic} from 'react'. Lub useFetcher dla local updates. useFetcher: const fetcher = useFetcher(). fetcher.submit(data, {method: 'post'}). fetcher.state — stan bez nawigacji. Like button bez redirect. Revalidation: po action automatyczny refetch loaders. shouldRevalidate: export function shouldRevalidate({nextUrl}: ShouldRevalidateFunctionArgs) { return nextUrl.pathname.startsWith('/dashboard') }. Selektywna rewalidacja. Optimistic przykład: const [optimisticLikes, setOptimistic] = useState(likes). fetcher.submit — server update. setOptimistic(likes + 1) — natychmiastowy. useEffect na fetcher.data — sync po odpowiedzi. Meta: export function meta({data}: Route.MetaArgs) { return [{title: data.title}, {name: 'description', content: data.desc}] }. Dynamiczne SEO meta.
Powiązane artykuły
Skontaktuj się z nami
Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.
Wyślij zapytanie
Telefon
+48 790 814 814
Pon-Pt: 9:00 - 18:00
adam@fotz.pl
Odpowiadamy w ciągu 24h
Adres
Plac Wolności 16
61-739 Poznań
Godziny pracy
Wolisz porozmawiać?
Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.
Zadzwoń teraz