remix
Remix full-stack React framework. Covers loaders, actions, nested routing, and progressive enhancement. Use when building Remix applications. USE WHEN: user mentions "Remix", asks about "loaders", "actions", "Remix forms", "nested routes in Remix", "progressive enhancement", "defer in Remix", "useFetcher", "Remix deployment" DO NOT USE FOR: React without Remix - use `frontend-react` instead; Next.js - use `nextjs-app-router` instead; Nuxt - use `nuxt3` instead; SvelteKit - use `sveltekit` instead
What this skill does
# Remix Core Knowledge
> **Full Reference**: See [advanced.md](advanced.md) for WebSocket integration, Server-Sent Events, Socket.IO patterns, and room management.
> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `remix` for comprehensive documentation.
## Route Module
```tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { useLoaderData, Form } from '@remix-run/react';
// Server: Load data
export async function loader({ params }: LoaderFunctionArgs) {
const user = await db.users.find(params.id);
if (!user) throw new Response('Not Found', { status: 404 });
return json({ user });
}
// Server: Handle mutations
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
await db.users.update({ name: formData.get('name') });
return json({ success: true });
}
// Client: UI
export default function UserPage() {
const { user } = useLoaderData<typeof loader>();
return (
<Form method="post">
<input name="name" defaultValue={user.name} />
<button type="submit">Save</button>
</Form>
);
}
```
## Key Concepts
| Concept | Purpose |
|---------|---------|
| `loader` | GET requests, data fetching |
| `action` | POST/PUT/DELETE mutations |
| `Form` | Progressive enhanced forms |
| `useLoaderData` | Access loader data |
| `useFetcher` | Non-navigation mutations |
## Nested Routes
```
routes/
├── _index.tsx → /
├── users.tsx → /users (layout)
├── users._index.tsx → /users (index)
└── users.$id.tsx → /users/:id
```
## Error Handling
```tsx
export function ErrorBoundary() {
const error = useRouteError();
return <div>Error: {error.message}</div>;
}
```
## Production Readiness
### Security
```typescript
// app/utils/csrf.server.ts
import { createCookie } from '@remix-run/node';
export const csrfCookie = createCookie('csrf', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
});
// app/root.tsx - Security headers
export const headers: HeadersFunction = () => ({
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': "default-src 'self'",
});
// Input validation
import { z } from 'zod';
const UserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const result = UserSchema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return json({ errors: result.error.flatten() }, { status: 400 });
}
// Process valid data
}
```
### Error Handling
```typescript
// app/root.tsx
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} {error.statusText}</h1>
<p>{error.data}</p>
</div>
);
}
return (
<div>
<h1>Unexpected Error</h1>
<p>{error instanceof Error ? error.message : 'Unknown error'}</p>
</div>
);
}
// Per-route error boundary
// app/routes/users.$id.tsx
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error) && error.status === 404) {
return <div>User not found</div>;
}
throw error; // Re-throw to parent boundary
}
```
### Performance
```typescript
// Streaming with defer
import { defer } from '@remix-run/node';
import { Await, useLoaderData } from '@remix-run/react';
export async function loader() {
const criticalData = await getCriticalData();
const slowData = getSlowData(); // Don't await
return defer({
criticalData,
slowData,
});
}
export default function Page() {
const { criticalData, slowData } = useLoaderData<typeof loader>();
return (
<div>
<div>{criticalData.name}</div>
<Suspense fallback={<Skeleton />}>
<Await resolve={slowData}>
{(data) => <SlowSection data={data} />}
</Await>
</Suspense>
</div>
);
}
// Caching
export const headers: HeadersFunction = () => ({
'Cache-Control': 'public, max-age=300, s-maxage=3600',
});
```
### Session Management
```typescript
// app/utils/session.server.ts
import { createCookieSessionStorage, redirect } from '@remix-run/node';
const sessionStorage = createCookieSessionStorage({
cookie: {
name: '__session',
httpOnly: true,
maxAge: 60 * 60 * 24 * 7, // 1 week
path: '/',
sameSite: 'lax',
secrets: [process.env.SESSION_SECRET!],
secure: process.env.NODE_ENV === 'production',
},
});
export async function requireUser(request: Request) {
const session = await sessionStorage.getSession(request.headers.get('Cookie'));
const userId = session.get('userId');
if (!userId) {
throw redirect('/login');
}
return userId;
}
```
### Testing
```typescript
// app/routes/users.test.ts
import { createRemixStub } from '@remix-run/testing';
import { render, screen } from '@testing-library/react';
import UsersPage, { loader } from './users';
describe('Users Page', () => {
it('renders users', async () => {
const RemixStub = createRemixStub([
{
path: '/',
Component: UsersPage,
loader: () => ({ users: [{ id: '1', name: 'John' }] }),
},
]);
render(<RemixStub />);
expect(await screen.findByText('John')).toBeInTheDocument();
});
});
// E2E with Playwright
test('user flow', async ({ page }) => {
await page.goto('/users');
await page.click('button:has-text("Add User")');
await page.fill('input[name="name"]', 'Jane');
await page.click('button:has-text("Save")');
await expect(page.locator('text=Jane')).toBeVisible();
});
```
### Monitoring Metrics
| Metric | Target |
|--------|--------|
| Time to First Byte | < 100ms |
| First Contentful Paint | < 1s |
| Cumulative Layout Shift | < 0.1 |
| Form submission time | < 500ms |
### Checklist
- [ ] Security headers in root
- [ ] CSRF protection for forms
- [ ] Input validation with Zod
- [ ] ErrorBoundary per route
- [ ] Streaming with defer
- [ ] Cache headers for static data
- [ ] Secure session cookies
- [ ] Rate limiting on actions
- [ ] Testing with @remix-run/testing
- [ ] Progressive enhancement verified
## When NOT to Use This Skill
This skill is for Remix (React meta-framework). DO NOT use for:
- **React without Remix**: Use `frontend-react` skill instead
- **Next.js (React meta-framework)**: Use `nextjs-app-router` skill instead
- **Nuxt (Vue meta-framework)**: Use `nuxt3` skill instead
- **SvelteKit**: Use `sveltekit` skill instead
- **Astro**: Use `astro` skill instead
- **React Router SPA**: Use `frontend-react` with React Router documentation
## Anti-Patterns
| Anti-Pattern | Why It's Wrong | Correct Approach |
|--------------|----------------|------------------|
| Fetching in useEffect | Client-side only, no SSR, waterfalls | Use loader function for data fetching |
| Not using Form component | No progressive enhancement | Use <Form> from @remix-run/react |
| Ignoring ErrorBoundary | Uncaught errors crash route | Export ErrorBoundary from each route |
| Putting business logic in component | Not server-side, harder to test | Move logic to loader/action functions |
| Not validating formData | Security risk, type errors | Validate with Zod in action function |
| Using fetch instead of fetcher | Full page reload on mutation | Use useFetcher for non-navigation mutations |
| No cache headers | Poor performance | Set Cache-Control headers in loader |
| Secrets in client code | Exposed to client, security risk | Keep secrets in loader/action only |
## Quick Troubleshooting
| Issue | Possible Cause | Solution |
|-------|----------------|----------|
| "useLoaderData returns undefined" | No loader exported | Export loader function from route |
| Form submission doesn't work | UsingRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.