Skip to content
cccskills
FreePublished on GitHub

react-routing

Routing libraries for React SPAs: React Router v6/v7 (most common), TanStack Router (typed, modern), wouter (minimal). Detect what's installed and apply matching patterns. Lazy loading, navigation guards, typed params. Use this skill to: - Configure routes (declarative or file-based). - Use navigation hooks (useNavigate, useParams, useSearchParams). - Lazy-load routes for code splitting. - Implement protected routes / auth guards. - Type-safe params via Zod or framework's built-ins. Do NOT use this skill for: - General React conventions (see react-conventions). - State management (see react-state-management). - Form handling (see react-forms). - Next.js routing (different model — see nextjs-plugin).

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md9.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

React Routing Patterns

Choose the routing library based on what's already in package.json. Don't introduce a new one without BA approval.

Detection

Marker (in dependencies)Library
react-router-domReact Router v6 (≤6.x) or v7
@tanstack/react-routerTanStack Router
wouterwouter (minimal alternative)
(none)Single-page; introduce only if BA spec adds multi-page

React Router v6 / v7

The de-facto standard. v7 unified react-router-dom and the framework features; for SPA usage the API is similar.

Declarative routes (most common)

// src/main.tsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import App from './App';
import UsersList from './pages/UsersList';
import UserDetail from './pages/UserDetail';
import Login from './pages/Login';
import NotFound from './pages/NotFound';

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    errorElement: <NotFound />,
    children: [
      { index: true, element: <Home /> },
      { path: 'users', element: <UsersList /> },
      { path: 'users/:id', element: <UserDetail /> },
      { path: 'login', element: <Login /> },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById('root')!).render(
  <RouterProvider router={router} />
);

<App /> is the layout; <Outlet /> inside renders the matched child route:

// src/App.tsx
import { Outlet } from 'react-router-dom';

export default function App() {
  return (
    <div>
      <Header />
      <main><Outlet /></main>
      <Footer />
    </div>
  );
}

Navigation hooks

import { Link, NavLink, useNavigate, useParams, useSearchParams, useLocation } from 'react-router-dom';

// Declarative — Link or NavLink
<Link to="/users">Users</Link>
<NavLink to="/users" className={({ isActive }) => isActive ? 'active' : ''}>Users</NavLink>

// Programmatic
const navigate = useNavigate();
navigate('/users');                          // push
navigate('/users', { replace: true });       // replace
navigate(-1);                                 // back

// Params from dynamic segments
const { id } = useParams<{ id: string }>();

// Search params (query string)
const [searchParams, setSearchParams] = useSearchParams();
const filter = searchParams.get('filter') ?? 'all';
setSearchParams({ filter: 'active' });

// Current location
const location = useLocation();
console.log(location.pathname, location.search, location.hash);

Loaders and actions (data router APIs)

In React Router v6.4+ / v7, route loaders fetch data before render:

// src/routes/users.tsx
import { LoaderFunction, useLoaderData } from 'react-router-dom';

export const loader: LoaderFunction = async () => {
  const res = await fetch('/api/users');
  if (!res.ok) throw new Response('Failed', { status: 500 });
  return res.json();
};

export function UsersList() {
  const users = useLoaderData() as User[];
  return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}

Wire in route config:

{ path: 'users', element: <UsersList />, loader: usersLoader }

For mutations:

import { ActionFunction, redirect, useActionData } from 'react-router-dom';

export const action: ActionFunction = async ({ request }) => {
  const formData = await request.formData();
  // ... validate, save
  return redirect('/users');
};

Loaders/actions are an alternative to TanStack Query — pick one per project. For most projects with TanStack Query, skip loaders; use useQuery inside the route component.

Lazy-loaded routes

import { lazy } from 'react';

const UsersList = lazy(() => import('./pages/UsersList'));

// In route config
{
  path: 'users',
  element: (
    <Suspense fallback={<Spinner />}>
      <UsersList />
    </Suspense>
  ),
}

For file-based code splitting, route config can use lazy loader:

{ path: 'users', lazy: () => import('./routes/users') }

The imported module exports { Component, loader, action }.

Protected routes (auth guards)

// src/routes/RequireAuth.tsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth';

export function RequireAuth() {
  const { user } = useAuth();
  const location = useLocation();
  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
  return <Outlet />;
}

Wrap protected routes:

{
  element: <RequireAuth />,
  children: [
    { path: 'dashboard', element: <Dashboard /> },
    { path: 'settings', element: <Settings /> },
  ],
}

Type-safe params with Zod

import { useParams } from 'react-router-dom';
import { z } from 'zod';

const ParamsSchema = z.object({ id: z.string().uuid() });

function UserDetail() {
  const params = useParams();
  const parsed = ParamsSchema.safeParse(params);
  if (!parsed.success) throw new Error('Invalid params');
  const { id } = parsed.data; // string (UUID)
  // ...
}

For library-level type safety, see TanStack Router below.

TanStack Router

Typed, modern. Code-based or file-based routing. Built-in search-param parsing and validation.

File-based setup

// src/routeTree.gen.ts (generated by @tanstack/router-plugin)

// src/routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router';

export const Route = createRootRoute({
  component: () => (
    <div>
      <Header />
      <Outlet />
    </div>
  ),
});

// src/routes/users.tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/users')({
  component: UsersPage,
  loader: () => fetchUsers(),
});

function UsersPage() {
  const users = Route.useLoaderData();
  return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}

// src/routes/users.$id.tsx — dynamic segment
export const Route = createFileRoute('/users/$id')({
  parseParams: (params) => ({ id: z.string().uuid().parse(params.id) }),
  component: UserDetail,
});

Route.useParams() returns the parsed (typed) params — no runtime check needed at the consumer.

Search params validation

export const Route = createFileRoute('/users')({
  validateSearch: z.object({
    filter: z.enum(['all', 'active', 'archived']).default('all'),
    page: z.number().int().min(1).default(1),
  }),
  component: UsersPage,
});

// In component
const { filter, page } = Route.useSearch();

Type-safe, validated search params at the route level.

Navigation

import { Link, useNavigate } from '@tanstack/react-router';

<Link to="/users" search={{ filter: 'active' }}>Active users</Link>;

const navigate = useNavigate();
navigate({ to: '/users/$id', params: { id: '123' }, search: { tab: 'overview' } });

Navigation is fully type-checked — wrong path or missing param is a compile error.

wouter

Minimal alternative (pnpm add wouter). ~3 KB. Pattern-based.

import { Route, Switch, Link, useLocation } from 'wouter';

function App() {
  return (
    <Switch>
      <Route path="/"><Home /></Route>
      <Route path="/users"><UsersList /></Route>
      <Route path="/users/:id">{(params) => <UserDetail id={params.id} />}</Route>
      <Route><NotFound /></Route>
    </Switch>
  );
}

const [location, setLocation] = useLocation();
setLocation('/users');

Good for very small apps. Misses nested routes with shared layouts (build them via composition).

Code splitting strategy

Routes are the natural boundary. Don't over-split:

  • Each top-level route → split.
  • Heavy modal/wizard inside a route → split.
  • Small page → don't bother.

For React Router:

{ path: 'users', lazy: () => import('./routes/users') }

For TanStack Router with file-based routes — splitting is built into the plugin.

Layout patterns

Layouts compose via parent routes:

// React Router
{
  element: <DashboardLayout />,
  path: '/dashboard',
  children: [
    { index: true, element: <DashboardHome /> },
    { path: 'analytics', element: <Analytics /> },
    { path: 'settings', element: <Settings /> },
  ],
}

<DashboardLayout /> renders <Outlet /> for children. Persists across navigation within the layout.

Anti-patterns

  • ❌ Mixing <a href="/internal"> with <Link> — full page reload defeats SPA benefits.
  • ❌ Storing data already in URL params in component state too — keep ONE source of truth.
  • ❌ Reading window.location.search directly — use useSearchParams or framework hook.
  • ❌ Programmatic navigation deep in business logic — pass a callback or use a hook at the page level.
  • ❌ Loaders that fetch data already cached by TanStack Query — pick one strategy per project.
  • ❌ Forgetting to wrap lazy-loaded routes in <Suspense> — error boundary catches the thrown promise oddly.
  • ❌ useNavigate inside a useEffect for redirects — use loaders or <Navigate> instead, or run on event handlers.
  • ❌ Long route trees in one file — split by feature.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc42Updated Oct 17, 2026

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc42Updated Oct 17, 2026

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc42Updated Oct 17, 2026

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc42Updated Oct 17, 2026

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc42Updated Oct 17, 2026

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

The English summary is being prepared. Showing the original description.

AratKruglik/claude-sdlc42Updated Oct 17, 2026

See all skills by AratKruglik

Report a problem with this skill