Skip to content
Back to skills

React Router Expert

ASecurity

Expert in React Router 7 framework mode (formerly Remix) — loaders, actions, file routes. Use when building web applications with react router.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 8, 2026
developmentgoreactnextjsnodeapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill react-router-expert --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of React Router Expert?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for React Router Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-react-router-expert/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-react-router-expert)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: react-router-expert
description: Expert in React Router 7 framework mode (formerly Remix) — loaders, actions, file routes. Use when building web applications with react router.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: modern-web
  tags: [react-router, remix, ssr, react, full-stack, loaders, actions]
---

# React Router Expert Mode

You are an expert in React Router v7. You build full-stack React apps using framework mode — the merged Remix + React Router with file-based routes, server loaders, and form-based actions.

## Core Competencies

### Three Modes (pick the right one)

- **Framework mode** — full-stack, SSR, file routes, loaders/actions (recommended for new apps)
- **Data mode** — programmatic routes + loaders without the framework wrapper
- **Declarative mode** — classic `<Routes>` for SPAs that don't need data primitives

### Framework Mode Core APIs

- `loader` — server-only data fetcher, removed from client bundle
- `clientLoader` — runs in the browser; can wrap or replace `loader`
- `action` / `clientAction` — mutations triggered by `<Form>` POSTs
- `useLoaderData()`, `useActionData()`, `useFetcher()`, `useNavigation()`
- Route Module exports: `default`, `loader`, `action`, `meta`, `links`, `headers`, `ErrorBoundary`, `HydrateFallback`
- `routes.ts` — central declaration using `route()`, `index()`, `layout()`, `prefix()`
- Auto-generated types: `Route.LoaderArgs`, `Route.ComponentProps`

## Approach

1. Use **framework mode** unless you specifically need just SPA routing
2. Treat `loader` + `action` as your data layer — push fetching/mutation off the client
3. Use `<Form method="post">` over `onSubmit` handlers; you get progressive enhancement free
4. Lean on automatic revalidation after actions — no manual cache busting
5. Use `clientLoader.hydrate = true` only when you need a separate browser-side fetch

## Key Patterns

### routes.ts

```ts
import { type RouteConfig, route, index, layout, prefix } from '@react-router/dev/routes';

export default [
  index('routes/home.tsx'),
  layout('routes/auth-layout.tsx', [
    route('login', 'routes/login.tsx'),
    route('signup', 'routes/signup.tsx'),
  ]),
  ...prefix('products', [
    index('routes/products/index.tsx'),
    route(':id', 'routes/products/show.tsx'),
  ]),
] satisfies RouteConfig;
```

### Loader + Component (server-side)

```tsx
// app/routes/products/show.tsx
import type { Route } from './+types/show';
import { db } from '~/db.server';

export async function loader({ params }: Route.LoaderArgs) {
  const product = await db.product.findUnique({ where: { id: params.id } });
  if (!product) throw new Response('Not Found', { status: 404 });
  return product;
}

export default function Product({ loaderData }: Route.ComponentProps) {
  return <h1>{loaderData.name}</h1>;
}
```

### Action with Form

```tsx
import type { Route } from './+types/edit';
import { Form, redirect } from 'react-router';

export async function action({ request, params }: Route.ActionArgs) {
  const fd = await request.formData();
  await db.product.update({
    where: { id: params.id },
    data: { name: String(fd.get('name')) },
  });
  return redirect(`/products/${params.id}`);
}

export default function Edit({ loaderData }: Route.ComponentProps) {
  return (
    <Form method="post">
      <input name="name" defaultValue={loaderData.name} />
      <button>Save</button>
    </Form>
  );
}
```

### clientLoader (hybrid)

```tsx
export async function loader({ params }: Route.LoaderArgs) {
  return db.product.findUnique({ where: { id: params.id } });
}

export async function clientLoader({ serverLoader, params }: Route.ClientLoaderArgs) {
  const [server, fresh] = await Promise.all([
    serverLoader(),
    fetch(`/api/products/${params.id}`).then((r) => r.json()),
  ]);
  return { ...server, ...fresh };
}
clientLoader.hydrate = true;

export function HydrateFallback() { return <p>Loading…</p>; }
```

### Optimistic UI with useFetcher

```tsx
import { useFetcher } from 'react-router';

function Like({ id, count }: { id: string; count: number }) {
  const fetcher = useFetcher();
  const optimistic = fetcher.formData ? count + 1 : count;
  return (
    <fetcher.Form method="post" action={`/posts/${id}/like`}>
      <button>{optimistic}</button>
    </fetcher.Form>
  );
}
```

### ErrorBoundary

```tsx
import { isRouteErrorResponse, useRouteError } from 'react-router';

export function ErrorBoundary() {
  const e = useRouteError();
  if (isRouteErrorResponse(e)) return <h1>{e.status} {e.statusText}</h1>;
  return <h1>Something went wrong</h1>;
}
```

## Common Pitfalls

- Importing server-only modules (`fs`, db drivers) from a component — wrap them in `.server.ts` files
- Using `useEffect` to fetch data inside a route module — that's what `loader` is for
- Forgetting that loaders **revalidate after every action** by default
- Mixing `Route.LoaderArgs` types from the wrong route file
- Returning non-serializable values from `loader` (functions, class instances)
- Using `<form>` instead of `<Form>` — you lose action wiring and progressive enhancement

## When to Use This Mode

- New React full-stack apps (Remix users — this is your upgrade path)
- Apps where progressive enhancement and SEO matter
- Migrating from Next.js Pages router and wanting a thinner abstraction
- Form-heavy apps (admin panels, CRUD)
- Apps deployed to multiple runtimes (Node, Bun, Cloudflare, Vercel)

## Sources

- [React Router data loading](https://reactrouter.com/start/framework/data-loading)
- [React Router actions](https://reactrouter.com/start/framework/actions)
- [Picking a Mode](https://reactrouter.com/start/modes)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.