Claude Md Example For Remix Fullstack (2026)
Remix has become one of the most popular fullstack React frameworks, offering a unique approach to web development with its focus on web standards, nested routing, and server-side rendering. Combining Claude Code with Remix through skill files creates a powerful development environment that understands both the frontend and backend aspects of your application.
This guide shows you how to create and use Claude skills specifically designed for Remix fullstack development. You’ll find practical examples, code snippets, and workflow patterns that accelerate your Remix projects.
Setting Up Claude Skills for Remix Development
Claude Code’s skill system uses Markdown files in ~/.claude/skills/ to provide context-specific guidance. For Remix projects, you want skills that understand loaders, actions, routes, and the fullstack nature of the framework.
First, verify your skills directory exists:
ls ~/.claude/skills/
Create a dedicated Remix skill if you don’t have one:
nano ~/.claude/skills/remix-fullstack.md
The reason you want a dedicated skill rather than relying on Claude’s general knowledge is repeatability. Claude already knows Remix. the skill ensures it applies Remix patterns consistently across your project rather than mixing in patterns from Next.js, Express, or older React Router conventions. A well-written skill anchors Claude to your project’s specific version, coding style, and architectural decisions.
Example Claude Skill for Remix Fullstack Development
Here is a practical skill template you can adapt:
Remix Fullstack Developer
You are an expert Remix developer. When I work on features:
1. Use loaders for server-side data fetching
2. Implement actions for form submissions and mutations
3. Follow Remix nested routing conventions
4. Handle errors with ErrorBoundary components
5. Use type-safe data with TypeScript generics
For data loading, always prefer server-side loaders:
This skill structure tells Claude how to approach Remix-specific challenges. The key is defining clear patterns for each Remix primitive.
But you can go further. A more complete skill adds your project’s database library, naming conventions, and the specific Remix version you’re targeting:
Remix Fullstack Developer
Stack: Remix v2, TypeScript, Prisma ORM, Tailwind CSS, deployed on Fly.io.
Core Patterns
Loaders: Always typed with `LoaderFunctionArgs`. Return json() with explicit type parameter. Throw Response for 404/403 cases.
Actions: Validate with Zod before touching the database. Return validation errors as `{ errors }`, never throw on validation failure.
Routes: Use file-based routing. Co-locate loader, action, component, and ErrorBoundary in the same file unless the file exceeds 200 lines.
Auth: Session management via remix-auth with cookie sessions. Every protected loader checks session before any DB call.
Naming Conventions
- Route files: `app/routes/resource.$id.tsx`
- Loader types: `type LoaderData = Awaited<ReturnType<typeof loader>>`
- Action types: `type ActionData = { errors?: ValidationErrors }`
This level of detail eliminates entire categories of back-and-forth. Claude won’t suggest JWT tokens when you use cookie sessions, won’t reach for fetch() inside components when you’ve established the loader pattern, and won’t return plain objects from actions when you’ve specified json().
Working with Loaders and Data Loading
Loaders are the backbone of Remix data fetching. When you create a skill, specify how Claude should handle server-side data:
// app/routes/projects.$projectId.tsx
import { json, LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
export async function loader({ params }: LoaderFunctionArgs) {
const project = await db.project.findUnique({
where: { id: params.projectId }
});
if (!project) {
throw new Response("Not Found", { status: 404 });
}
return json({ project });
}
export default function ProjectRoute() {
const { project } = useLoaderData<typeof loader>();
return <ProjectDetails project={project} />;
}
When using Claude, reference your skill to get proper loader patterns. The skill should understand when to use useLoaderData, how to type loaders properly, and when to throw responses versus returning errors.
A common mistake developers make without a skill: asking Claude to “fetch project data for the route” and getting back a useEffect with a fetch() call. That’s the React SPA pattern, not Remix. Your skill explicitly rules that out.
For routes that load multiple resources, your skill should also guide how to handle parallel data fetching:
// app/routes/dashboard.tsx
import { json, LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
export async function loader({ request }: LoaderFunctionArgs) {
const session = await getSession(request.headers.get("Cookie"));
const userId = session.get("userId");
if (!userId) {
throw redirect("/login");
}
// Parallel DB queries. don't await sequentially
const [projects, recentActivity, notifications] = await Promise.all([
db.project.findMany({ where: { userId } }),
db.activityLog.findMany({ where: { userId }, take: 10 }),
db.notification.findMany({ where: { userId, read: false } })
]);
return json({ projects, recentActivity, notifications });
}
Document this pattern in your skill so Claude reaches for Promise.all automatically instead of three sequential awaits that triple your loader latency.
Handling Form Actions
Remix actions process form submissions on the server. Your Claude skill should guide proper action implementation:
// app/routes/contact.tsx
import { ActionFunctionArgs, redirect } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const email = formData.get("email");
const message = formData.get("message");
const errors: Record<string, string> = {};
if (!email) errors.email = "Email is required";
if (!message) errors.message = "Message is required";
if (Object.keys(errors).length > 0) {
return { errors };
}
await sendEmail({ email, message });
return redirect("/contact/success");
}
export default function Contact() {
const actionData = useActionData<typeof action>();
return (
<Form method="post">
<input name="email" type="email" />
{actionData?.errors?.email && <span>{actionData.errors.email}</span>}
<textarea name="message" />
<button type="submit">Send</button>
</Form>
);
}
Your Claude skill should emphasize validation within actions, proper error return patterns, and redirect handling.
For more complex mutations, your skill can reference Zod-based validation patterns that Claude will then apply consistently:
// app/routes/projects.new.tsx
import { z } from "zod";
import { parseWithZod } from "@conform-to/zod";
const schema = z.object({
name: z.string().min(1).max(100),
description: z.string().max(500).optional(),
visibility: z.enum(["public", "private"]),
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });
if (submission.status !== "success") {
return json(submission.reply(), { status: 400 });
}
const project = await db.project.create({
data: {
...submission.value,
userId: session.get("userId"),
},
});
return redirect(`/projects/${project.id}`);
}
Including a library preference like Conform + Zod in your skill means Claude generates this pattern rather than a hand-rolled validation object every time.
Type-Safe Data with TypeScript
Remix provides excellent TypeScript support through generic typing. Reference skills that cover these patterns:
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
// Strongly typed loader
export async function loader() {
const users = await db.user.findMany();
return json<{ users: User[] }>({ users });
}
// Type inference from loader
export default function Users() {
const { users } = useLoaderData<typeof loader>();
// users is properly typed as User[]
return <UserList users={users} />;
}
This pattern ensures end-to-end type safety from your database through your loader to your React components.
If your skill specifies this approach, Claude will also handle the serialization edge cases Remix creates. The useLoaderData<typeof loader>() pattern serializes dates as strings over the wire, so your skill should note that Date objects become string in the client component:
// Loader returns Date from Prisma
export async function loader() {
const posts = await db.post.findMany();
return json({ posts });
}
// In component, createdAt is string, not Date
export default function Posts() {
const { posts } = useLoaderData<typeof loader>();
// posts[0].createdAt is string. convert before display
return posts.map(post => (
<time key={post.id} dateTime={post.createdAt}>
{new Date(post.createdAt).toLocaleDateString()}
</time>
));
}
Documenting this in your skill prevents Claude from returning post.createdAt.toLocaleDateString(). a runtime error because the client receives a string, not a Date instance.
Integration with Claude Skills
Combine your Remix skill with other Claude skills for comprehensive coverage. The frontend-design skill helps with component styling, while tdd assists with test-driven development for your route modules:
/remix-fullstack
/frontend-design
/tdd
The pdf skill proves useful when generating reports from your Remix application data. The supermemory skill helps maintain context across complex feature development.
Here is how different skill combinations apply to real Remix development scenarios:
| Task | Primary Skill | Supporting Skill |
|---|---|---|
| Build a new route with CRUD | remix-fullstack |
. |
| Add data visualization to a dashboard | remix-fullstack |
frontend-design |
| Test loader and action logic | remix-fullstack |
tdd |
| Export report data as PDF | remix-fullstack |
pdf |
| Long feature spanning multiple sessions | remix-fullstack |
supermemory |
| Optimize database queries in loaders | remix-fullstack |
database |
Skill combination works because each skill provides focused guidance. When you invoke /tdd alongside /remix-fullstack, Claude knows to write Vitest tests for loaders using createRemixStub. the correct testing approach for Remix. rather than shallow rendering tests that miss the server-side behavior entirely.
Error Handling Patterns
Remix provides ErrorBoundary components for graceful error handling. Your Claude skill should include these patterns:
// app/routes/projects.$projectId.tsx
export function ErrorBoundary() {
const error = useRouteError();
if (error instanceof Response) {
return (
<div>
<h1>{error.status} - {error.statusText}</h1>
<p>Project not found or unavailable.</p>
</div>
);
}
return <div>Unexpected error occurred</div>;
}
Your skill should remind you to implement ErrorBoundary in every route, especially for routes with loaders that might fail.
A more production-ready ErrorBoundary pattern handles different HTTP status codes with appropriate user messaging:
// app/routes/projects.$projectId.tsx
import { useRouteError, isRouteErrorResponse, Link } from "@remix-run/react";
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
if (error.status === 404) {
return (
<div className="error-container">
<h1>Project Not Found</h1>
<p>This project doesn't exist or has been deleted.</p>
<Link to="/projects">Back to Projects</Link>
</div>
);
}
if (error.status === 403) {
return (
<div className="error-container">
<h1>Access Denied</h1>
<p>You don't have permission to view this project.</p>
<Link to="/projects">Back to Projects</Link>
</div>
);
}
return (
<div className="error-container">
<h1>Error {error.status}</h1>
<p>{error.data?.message || "Something went wrong."}</p>
</div>
);
}
// Unexpected errors (thrown Error instances, etc.)
if (error instanceof Error) {
return (
<div className="error-container">
<h1>Unexpected Error</h1>
<p>An unexpected error occurred. Our team has been notified.</p>
</div>
);
}
return <div>Unknown error occurred.</div>;
}
Adding this full pattern to your skill means Claude generates production-ready error handling instead of the minimal version that only handles one case.
Nested Routes and Layout Data
One area where a Remix-specific skill pays off most is nested routing. Developers coming from other frameworks often ask Claude for patterns that fight against Remix’s design. Your skill should establish the correct mental model:
// app/routes/projects.tsx. parent layout route
import { Outlet, useLoaderData, NavLink } from "@remix-run/react";
export async function loader({ request }: LoaderFunctionArgs) {
const session = await requireSession(request);
// Load project list once at layout level
const projects = await db.project.findMany({
where: { userId: session.userId },
select: { id: true, name: true }
});
return json({ projects });
}
export default function ProjectsLayout() {
const { projects } = useLoaderData<typeof loader>();
return (
<div className="projects-layout">
<nav>
{projects.map(p => (
<NavLink key={p.id} to={`/projects/${p.id}`}>
{p.name}
</NavLink>
))}
</nav>
<main>
<Outlet />
</main>
</div>
);
}
// app/routes/projects.$projectId.tsx. child route
// Only loads project detail. project list is already in parent
export async function loader({ params, request }: LoaderFunctionArgs) {
const session = await requireSession(request);
const project = await db.project.findUnique({
where: { id: params.projectId, userId: session.userId }
});
if (!project) throw new Response("Not Found", { status: 404 });
return json({ project });
}
Include a note in your skill: “Use parent routes to load shared data once. Child routes load only their specific data. Never duplicate fetches between parent and child.”
Pending UI and Optimistic Updates
Remix’s useNavigation and useFetcher hooks provide the building blocks for responsive UI during form submissions. Add these patterns to your skill so Claude includes them automatically:
// Pending state during form submission
import { Form, useNavigation } from "@remix-run/react";
export default function NewProject() {
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post">
<input name="name" placeholder="Project name" />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Creating..." : "Create Project"}
</button>
</Form>
);
}
For inline updates that shouldn’t trigger a full page navigation, the useFetcher pattern keeps the interaction scoped:
// Inline status toggle without page navigation
import { useFetcher } from "@remix-run/react";
function ProjectStatusToggle({ project }: { project: Project }) {
const fetcher = useFetcher();
const status = fetcher.formData?.get("status") ?? project.status;
return (
<fetcher.Form method="post" action={`/projects/${project.id}/status`}>
<button
name="status"
value={status === "active" ? "archived" : "active"}
>
{status === "active" ? "Archive" : "Restore"}
</button>
</fetcher.Form>
);
}
Practical Workflow
When starting a new Remix feature, reference your skill file:
- Describe the feature to Claude with your skill active
- Request loader and action implementation
- Ask for ErrorBoundary and pending UI
- Verify TypeScript types flow correctly
This approach ensures consistent patterns across your entire Remix application. The skill serves as a reminder for best practices that might otherwise be overlooked during rapid development.
A realistic workflow looks like this: you tell Claude “Add a project archiving feature. Users can toggle a project between active and archived state. Show archived projects separately in the sidebar.” With your skill active, Claude produces the route file with loader (fetches projects with status filter), action (validates input, updates DB, returns json), ErrorBoundary, pending UI using useFetcher, and the TypeScript types wired through. all aligned to your stack and conventions without you specifying each piece individually.
Without the skill, you’d need to specify Prisma over raw SQL, Zod over manual validation, cookie sessions over JWT, and a dozen other decisions on every request. The skill captures those decisions once.
Conclusion
Creating Claude skills for Remix fullstack development significantly improves your productivity. Skills that understand loaders, actions, nested routing, and error handling provide context-aware assistance throughout your project. The more detail you invest in your skill. libraries, conventions, error handling approaches, TypeScript patterns. the less guidance you need to provide per request and the more consistently Claude applies your architectural decisions.
Combine with complementary skills like frontend-design, tdd, and supermemory for comprehensive development support. Start with the basic template, then extend it each time you correct Claude’s output. Within a few sessions, your skill will encode enough project-specific knowledge that Claude feels like a collaborator who has been working on your codebase from the start.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Best Practices Guide. Production-ready Claude Code guidelines and patterns
- Claude MD Example for Elixir Phoenix Application
- Claude MD Example for Laravel PHP Application
- Building a REST API with Claude Code Tutorial
- CLAUDE.md Example for Rails and Ruby Apps
- CLAUDE.md Example for Elixir + Phoenix + LiveView — Production Template (2026)
- CLAUDE.md Example for Node.js + Express + Prisma — Production Template (2026)
- CLAUDE.md Example for Android + Kotlin + Jetpack Compose — Production Template (2026)
- CLAUDE.md Example for Flutter + Dart + Riverpod — Production Template (2026)
- CLAUDE.md Example for Laravel + PHP — Production Template (2026)
- CLAUDE.md Example for Rails + Turbo + Stimulus — Production Template (2026)
- CLAUDE.md Example for NestJS + TypeORM — Production Template (2026)
Built by theluckystrike. More at zovo.one
Find the right skill → Browse 155+ skills in our Skill Finder.