react-router

📁 grahamcrackers/skills 📅 2 days ago
3
总安装量
2
周安装量
#56509
全站排名
安装命令
npx skills add https://github.com/grahamcrackers/skills --skill react-router

Agent 安装分布

amp 2
gemini-cli 2
github-copilot 2
codex 2
kimi-cli 2
cursor 2

Skill 文档

React Router v7 Best Practices

Modes

React Router v7 offers two modes:

Mode Use When
Declarative (SPA) Client-side only app, no SSR needed
Framework Full-stack with SSR, loaders, and actions (Remix-style)

This skill covers declarative/SPA mode. Use framework mode when you need SSR.

Setup (SPA Mode)

npm install react-router
import { BrowserRouter, Routes, Route } from "react-router";

function App() {
    return (
        <BrowserRouter>
            <Routes>
                <Route element={<Layout />}>
                    <Route index element={<Home />} />
                    <Route path="users" element={<Users />} />
                    <Route path="users/:userId" element={<UserDetail />} />
                    <Route path="*" element={<NotFound />} />
                </Route>
            </Routes>
        </BrowserRouter>
    );
}

Route Configuration

Object-Based Routes

import { createBrowserRouter, RouterProvider } from "react-router";

const router = createBrowserRouter([
    {
        element: <Layout />,
        errorElement: <ErrorPage />,
        children: [
            { index: true, element: <Home /> },
            { path: "users", element: <Users /> },
            {
                path: "users/:userId",
                element: <UserDetail />,
                loader: userLoader,
            },
        ],
    },
]);

function App() {
    return <RouterProvider router={router} />;
}

Object-based config with createBrowserRouter is recommended — it enables loaders, actions, and error boundaries.

Nested Routes and Layouts

<Route element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="analytics" element={<Analytics />} />
    <Route path="settings" element={<Settings />} />
</Route>

The layout component renders an <Outlet /> where child routes appear:

function DashboardLayout() {
    return (
        <div>
            <DashboardSidebar />
            <main>
                <Outlet />
            </main>
        </div>
    );
}

Pathless Layout Routes

Wrap routes in a layout without adding a URL segment:

<Route element={<AuthLayout />}>
    {/* These routes require authentication */}
    <Route path="dashboard" element={<Dashboard />} />
    <Route path="profile" element={<Profile />} />
</Route>

Route Parameters

import { useParams } from "react-router";

function UserDetail() {
    const { userId } = useParams<{ userId: string }>();
    // fetch and render user
}

Optional and Catch-All

// Optional parameter
<Route path="users/:userId?" element={<Users />} />

// Catch-all / splat
<Route path="files/*" element={<FileViewer />} />

Access splat segments with useParams()["*"].

Search Params

import { useSearchParams } from "react-router";

function UserList() {
    const [searchParams, setSearchParams] = useSearchParams();
    const page = Number(searchParams.get("page") ?? "1");
    const sort = searchParams.get("sort") ?? "name";

    function setPage(newPage: number) {
        setSearchParams((prev) => {
            prev.set("page", String(newPage));
            return prev;
        });
    }

    return <List page={page} sort={sort} onPageChange={setPage} />;
}

Navigation

Links

import { Link, NavLink } from "react-router";

<Link to="/users/123">View User</Link>

<NavLink
  to="/dashboard"
  className={({ isActive }) => (isActive ? "active" : "")}
>
  Dashboard
</NavLink>

NavLink provides isActive and isPending states for styling active links.

Programmatic Navigation

import { useNavigate } from "react-router";

const navigate = useNavigate();

navigate("/users/123");
navigate("/users", { replace: true });
navigate(-1); // go back
navigate("/login", { state: { from: location.pathname } });

Redirects

import { Navigate } from "react-router";

<Route path="old-path" element={<Navigate to="/new-path" replace />} />;

Loaders

Fetch data before a route renders (requires createBrowserRouter):

async function userLoader({ params }: LoaderFunctionArgs) {
  const user = await api.users.getById(params.userId!);
  if (!user) throw new Response("Not Found", { status: 404 });
  return user;
}

// In route config
{ path: "users/:userId", element: <UserDetail />, loader: userLoader }

Access loader data in the component:

import { useLoaderData } from "react-router";

function UserDetail() {
    const user = useLoaderData() as User;
    return <div>{user.name}</div>;
}

Actions

Handle form submissions and mutations:

async function createUserAction({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const user = await api.users.create(Object.fromEntries(formData));
  return redirect(`/users/${user.id}`);
}

// In route config
{ path: "users/new", element: <CreateUser />, action: createUserAction }
import { Form, useActionData, useNavigation } from "react-router";

function CreateUser() {
    const errors = useActionData() as ValidationErrors | undefined;
    const navigation = useNavigation();
    const isSubmitting = navigation.state === "submitting";

    return (
        <Form method="post">
            <input name="name" />
            {errors?.name && <span>{errors.name}</span>}
            <button type="submit" disabled={isSubmitting}>
                Create
            </button>
        </Form>
    );
}

Error Handling

{
  path: "users/:userId",
  element: <UserDetail />,
  loader: userLoader,
  errorElement: <UserError />,
}
import { useRouteError, isRouteErrorResponse } from "react-router";

function UserError() {
    const error = useRouteError();

    if (isRouteErrorResponse(error)) {
        return (
            <div>
                {error.status}: {error.statusText}
            </div>
        );
    }

    return <div>Something went wrong</div>;
}

Error boundaries catch errors from loaders, actions, and rendering. They bubble up to the nearest parent errorElement.

Code Splitting

import { lazy } from "react";

const Dashboard = lazy(() => import("./pages/dashboard"));

const router = createBrowserRouter([
    {
        path: "dashboard",
        element: (
            <Suspense fallback={<DashboardSkeleton />}>
                <Dashboard />
            </Suspense>
        ),
    },
]);

Protected Routes

function RequireAuth({ children }: { children: React.ReactNode }) {
    const { user } = useAuth();
    const location = useLocation();

    if (!user) {
        return <Navigate to="/login" state={{ from: location }} replace />;
    }

    return children;
}

// Usage
<Route
    path="dashboard"
    element={
        <RequireAuth>
            <Dashboard />
        </RequireAuth>
    }
/>;

Or with loaders:

function protectedLoader({ request }: LoaderFunctionArgs) {
    const user = getUser();
    if (!user) throw redirect("/login");
    return user;
}

Scroll Restoration

import { ScrollRestoration } from "react-router";

function Layout() {
    return (
        <>
            <Outlet />
            <ScrollRestoration />
        </>
    );
}

Place ScrollRestoration once in your root layout.