| name | remix |
| description | Remix patterns including loaders, actions, nested routing, progressive enhancement, and deployment strategies. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
| graph | {"domains":["domain:web-development"],"specializations":["specialization:web-development"],"skillAreas":["skill-area:server-side-rendering","skill-area:data-fetching-caching"],"roles":["role:fullstack-engineer"],"topics":["topic:progressive-enhancement"]} |
Remix Skill
Expert assistance for building full-stack applications with Remix.
Capabilities
- Implement loaders for data fetching
- Create actions for mutations
- Configure nested routing with outlets
- Build progressively enhanced forms
- Handle errors and boundaries
- Set up deployment for various platforms
Usage
Invoke this skill when you need to:
- Build full-stack React applications
- Implement progressive enhancement
- Create nested layouts with data
- Handle form submissions properly
- Deploy to edge platforms
Inputs
| Parameter | Type | Required | Description |
|---|
| routePath | string | Yes | Route path |
| hasLoader | boolean | No | Include loader |
| hasAction | boolean | No | Include action |
| nested | boolean | No | Has nested routes |
Route Patterns
Loader and Action
import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
import { json, redirect } from '@remix-run/node';
import { useLoaderData, Form, useNavigation } from '@remix-run/react';
import { db } from '~/utils/db.server';
import { requireUser } from '~/utils/session.server';
export async function loader({ request }: LoaderFunctionArgs) {
await requireUser(request);
const url = new URL(request.url);
const search = url.searchParams.get('search') || '';
const users = await db.user.findMany({
where: search ? { name: { contains: search } } : undefined,
orderBy: { name: 'asc' },
});
return json({ users, search });
}
export () {
(request);
formData = request.();
intent = formData.();
(intent === ) {
name = formData.() ;
email = formData.() ;
(!name || !email) {
({ : }, { : });
}
db..({ : { name, email } });
();
}
(intent === ) {
id = formData.() ;
db..({ : { id } });
({ : });
}
({ : }, { : });
}
() {
{ users, search } = useLoaderData< loader>();
navigation = ();
isSearching = navigation. === &&
navigation.. === ;
(
);
}
Nested Routes
import { Outlet, NavLink } from '@remix-run/react';
export default function Dashboard() {
return (
<div className="dashboard">
<nav>
<NavLink to="/dashboard" end>Overview</NavLink>
<NavLink to="/dashboard/analytics">Analytics</NavLink>
<NavLink to="/dashboard/settings">Settings</NavLink>
</nav>
<main>
<Outlet />
</main>
</div>
);
}
export default function DashboardIndex() {
return <h2>Dashboard Overview</>;
}
() {
analytics = ();
({ analytics });
}
() {
{ analytics } = useLoaderData< loader>();
;
}
Error Boundaries
import { useRouteError, isRouteErrorResponse } from '@remix-run/react';
export async function loader({ params }: LoaderFunctionArgs) {
const user = await db.user.findUnique({
where: { id: params.userId },
});
if (!user) {
throw new Response('User not found', { status: 404 });
}
return json({ user });
}
export default function User() {
const { user } = useLoaderData<typeof loader>();
return <UserProfile user={user} />;
}
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div className="error">
{error.status} {error.statusText}
{error.data}
);
}
(
);
}
Optimistic UI
import { useFetcher } from '@remix-run/react';
function TodoItem({ todo }: { todo: Todo }) {
const fetcher = useFetcher();
const isDeleting = fetcher.state !== 'idle' &&
fetcher.formData?.get('intent') === 'delete';
const isToggling = fetcher.state !== 'idle' &&
fetcher.formData?.get('intent') === 'toggle';
const completed = isToggling
? !todo.completed
: todo.completed;
if (isDeleting) return null;
return (
<li style={{ opacity: fetcher.state !== 'idle' ? 0.5 : 1 }}>
<fetcher.Form method="post">
<input type="hidden" name="id" value={todo.id} />
{completed ? '✓' : '○'}
{todo.title}
×
);
}
Session and Authentication
import { createCookieSessionStorage, redirect } from '@remix-run/node';
const sessionStorage = createCookieSessionStorage({
cookie: {
name: '__session',
httpOnly: true,
path: '/',
sameSite: 'lax',
secrets: [process.env.SESSION_SECRET!],
secure: process.env.NODE_ENV === 'production',
},
});
export async function createUserSession(userId: string, redirectTo: string) {
const session = await sessionStorage.getSession();
session.set('userId', userId);
return redirect(redirectTo, {
headers: {
'Set-Cookie': await sessionStorage.commitSession(session),
},
});
}
export async function getUserId(request: ) {
session = .(
request..()
);
session.();
}
() {
userId = (request);
(!userId) {
();
}
userId;
}
() {
session = .(
request..()
);
(, {
: {
: .(session),
},
});
}
Best Practices
- Use loaders for GET requests (data fetching)
- Use actions for POST/PUT/DELETE (mutations)
- Leverage progressive enhancement with Form
- Use useFetcher for non-navigation mutations
- Implement proper error boundaries
Target Processes
- remix-full-stack
- progressive-enhancement
- edge-deployment
- form-handling