Claude Code Plugins

Community-maintained marketplace

Feedback

Create React Router 7 routes with proper type imports, loaders, and actions. Use when adding new pages, API endpoints, layouts, or route files.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name create-route
description Create React Router 7 routes with proper type imports, loaders, and actions. Use when adding new pages, API endpoints, layouts, or route files.

Create Route

Creates React Router 7 routes following Iridium's config-based routing patterns with proper type safety.

When to Use

  • Creating new pages or views
  • Adding API endpoints
  • Creating layout routes with <Outlet />
  • User asks to "add a route", "create a page", or "add an endpoint"

Critical Rule #1: Route Type Imports

THE MOST IMPORTANT RULE - NEVER BREAK THIS:

// ✅ ALWAYS use this exact pattern:
import type { Route } from './+types/my-route';

// ❌ NEVER use relative paths:
import type { Route } from '../+types/my-route';   // WRONG!
import type { Route } from '../../+types/my-route'; // WRONG!

If you see TypeScript errors about missing ./+types/[routeName] modules:

  1. Run npm run typecheck to generate types
  2. NEVER try to "fix" it by changing the import path

Critical Rule #2: Destructure Directly

// ✅ CORRECT - destructure in function signature
export async function action({ request, params }: Route.ActionArgs) {
    const formData = await request.formData();
}

// ❌ WRONG - intermediate variable
export async function action(args: Route.ActionArgs) {
    const { request } = args;  // Don't do this!
}

Critical Rule #3: Access Data via Props

// ✅ CORRECT - use loaderData prop
export default function MyPage({ loaderData }: Route.ComponentProps) {
    return <div>{loaderData.user.name}</div>;
}

// ❌ WRONG - old hook pattern
export default function MyPage() {
    const data = useLoaderData();  // DON'T USE THIS!
}

Route Module Pattern

Page Route (with UI)

import type { Route } from './+types/my-page';
import { data, redirect } from 'react-router';

// Server data loading (GET requests)
export async function loader({ request, params }: Route.LoaderArgs) {
    const user = await requireUser(request);
    const items = await getItems(user.id);
    return { items };
}

// Form handling (POST/PUT/DELETE)
export async function action({ request, params }: Route.ActionArgs) {
    const user = await requireUser(request);
    const formData = await request.formData();

    if (request.method === 'POST') {
        await createItem(formData);
        return redirect('/items');
    }

    if (request.method === 'DELETE') {
        await deleteItem(formData.get('id') as string);
        return data({ success: true });
    }

    return null;
}

// Component - access data via props
export default function MyPage({ loaderData }: Route.ComponentProps) {
    return (
        <>
            <title>Page Title | Iridium</title>
            <meta name="description" content="Page description" />
            {/* Page content */}
        </>
    );
}

API Route (no UI)

import type { Route } from './+types/my-api';
import { data } from 'react-router';

// GET requests
export async function loader({ request }: Route.LoaderArgs) {
    const user = await requireUser(request);
    const items = await getItems(user.id);
    return data({ items });
}

// POST/PUT/DELETE requests
export async function action({ request }: Route.ActionArgs) {
    const user = await requireUser(request);

    if (request.method === 'POST') {
        const body = await request.json();
        const result = await createItem(body);
        return data({ result });
    }

    if (request.method === 'PUT') {
        const body = await request.json();
        const result = await updateItem(body);
        return data({ result });
    }

    if (request.method === 'DELETE') {
        const body = await request.json();
        await deleteItem(body.id);
        return data({ success: true });
    }

    return data({ error: 'Method not allowed' }, { status: 405 });
}

Layout Route (with Outlet)

import type { Route } from './+types/my-layout';
import { Outlet } from 'react-router';

export default function MyLayout({ loaderData }: Route.ComponentProps) {
    return (
        <div className="layout">
            <nav>{/* Navigation */}</nav>
            <main>
                <Outlet />  {/* ✅ Child routes render here */}
            </main>
        </div>
    );
}

// ❌ NEVER use children prop - it doesn't exist
// export default function MyLayout({ children }: Route.ComponentProps)

Register in routes.ts

After creating a route file, add it to app/routes.ts:

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

export default [
    // Page routes
    route(Paths.MY_PAGE, 'routes/my-page.tsx'),

    // Layout with children
    layout('routes/my-layout.tsx', [
        index('routes/my-layout-index.tsx'),
        route('child', 'routes/my-layout-child.tsx'),
    ]),

    // API routes
    ...prefix(Paths.API, [
        route('my-endpoint', 'routes/api/my-endpoint.ts'),
    ]),
] satisfies RouteConfig;

Type-Safe URLs with href()

import { Link, href, redirect } from 'react-router';

// Static routes
<Link to={href('/products')}>Products</Link>

// Dynamic routes - TYPE SAFE
<Link to={href('/products/:id', { id: product.id })}>View</Link>

// In redirects
return redirect(href('/products/:id', { id: newId }));

// ❌ NEVER manually construct URLs
<Link to={`/products/${id}`}>View</Link>  // No type safety!

File Naming

✅ GOOD - kebab-case, directories:
routes/my-page.tsx
routes/api/my-endpoint.ts
routes/dashboard/settings.tsx

❌ BAD - flat routing with $ or periods:
routes/dashboard.$id.tsx
routes/dashboard.settings.tsx

Meta Tags (React 19 Pattern)

export default function MyPage({ loaderData }: Route.ComponentProps) {
    return (
        <>
            <title>Page Title | Iridium</title>
            <meta name="description" content="Description" />
            {/* Page content */}
        </>
    );
}

// ❌ NEVER use meta() export - it's the old pattern

After Creating a Route

  1. Register in routes.ts - Add the route configuration
  2. Run npm run typecheck - Generate route types
  3. Add to Paths constant if reusable (in app/constants.ts)

Anti-Patterns

  • ❌ Using ../+types/ relative imports (always ./+types/)
  • ❌ Using useLoaderData() hook (use loaderData prop)
  • ❌ Creating intermediate variables for destructuring
  • ❌ Using meta() export (use React 19 JSX tags)
  • ❌ Using children in layout routes (use <Outlet />)
  • ❌ Manual URL construction (use href())
  • ❌ Using file-based routing conventions with $
  • ❌ Calling Prisma directly (use model layer)

Templates

Full Reference

See .github/instructions/react-router.instructions.md for comprehensive documentation including streaming, error boundaries, and advanced patterns.