remix

Expert guidance for Remix development with TypeScript, loaders/actions, nested routes, and full-stack web application best practices

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "remix" with this command: npx skills add mindrally/skills/mindrally-skills-remix

Remix Development

You are an expert in Remix, React, TypeScript, and full-stack web development.

Key Principles

  • Write concise, technical Remix code with accurate TypeScript examples
  • Embrace progressive enhancement and web standards
  • Use loaders for data fetching and actions for mutations
  • Leverage nested routes for code organization
  • Prioritize server-side rendering and web fundamentals

Project Structure

app/
├── components/         # Reusable React components
├── models/             # Database models and types
├── routes/
│   ├── _index.tsx      # / route
│   ├── about.tsx       # /about route
│   └── posts/
│       ├── _index.tsx  # /posts route
│       └── $slug.tsx   # /posts/:slug route
├── styles/             # CSS files
├── utils/              # Utility functions
├── entry.client.tsx    # Client entry
├── entry.server.tsx    # Server entry
└── root.tsx            # Root layout

Loaders

Basic Loader

import type { LoaderFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await getPost(params.slug);

  if (!post) {
    throw new Response('Not Found', { status: 404 });
  }

  return json({ post });
}

export default function PostRoute() {
  const { post } = useLoaderData<typeof loader>();

  return <article>{post.content}</article>;
}

Loader with Authentication

import { redirect } from '@remix-run/node';
import { getUser } from '~/utils/session.server';

export async function loader({ request }: LoaderFunctionArgs) {
  const user = await getUser(request);

  if (!user) {
    throw redirect('/login');
  }

  return json({ user });
}

Actions

Form Handling

import type { ActionFunctionArgs } from '@remix-run/node';
import { json, redirect } from '@remix-run/node';

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const title = formData.get('title');
  const content = formData.get('content');

  const errors: Record<string, string> = {};

  if (!title) {
    errors.title = 'Title is required';
  }

  if (Object.keys(errors).length > 0) {
    return json({ errors }, { status: 400 });
  }

  const post = await createPost({ title, content });

  return redirect(`/posts/${post.slug}`);
}

Using Action Data

import { useActionData, Form } from '@remix-run/react';

export default function NewPost() {
  const actionData = useActionData<typeof action>();

  return (
    <Form method="post">
      <input name="title" type="text" />
      {actionData?.errors?.title && (
        <p className="error">{actionData.errors.title}</p>
      )}
      <textarea name="content" />
      <button type="submit">Create Post</button>
    </Form>
  );
}

Nested Routes

Layout Routes

// routes/dashboard.tsx (layout)
import { Outlet } from '@remix-run/react';

export default function DashboardLayout() {
  return (
    <div className="dashboard">
      <nav>
        <Link to="/dashboard">Overview</Link>
        <Link to="/dashboard/settings">Settings</Link>
      </nav>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

Pathless Layouts

// routes/_auth.tsx (pathless layout for /login, /register)
export default function AuthLayout() {
  return (
    <div className="auth-container">
      <Outlet />
    </div>
  );
}

useFetcher

Non-Navigation Fetches

import { useFetcher } from '@remix-run/react';

export default function LikeButton({ postId }: { postId: string }) {
  const fetcher = useFetcher();
  const isLiking = fetcher.state !== 'idle';

  return (
    <fetcher.Form method="post" action="/api/like">
      <input type="hidden" name="postId" value={postId} />
      <button type="submit" disabled={isLiking}>
        {isLiking ? 'Liking...' : 'Like'}
      </button>
    </fetcher.Form>
  );
}

Optimistic UI

export default function TodoItem({ todo }: { todo: Todo }) {
  const fetcher = useFetcher();

  const isDeleting = fetcher.formData?.get('_action') === 'delete';

  if (isDeleting) {
    return null; // Optimistically remove
  }

  return (
    <li>
      {todo.title}
      <fetcher.Form method="post">
        <input type="hidden" name="_action" value="delete" />
        <input type="hidden" name="id" value={todo.id} />
        <button type="submit">Delete</button>
      </fetcher.Form>
    </li>
  );
}

Error Boundaries

import { useRouteError, isRouteErrorResponse } from '@remix-run/react';

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>Error</h1>
      <p>{error instanceof Error ? error.message : 'Unknown error'}</p>
    </div>
  );
}

Resource Routes

// routes/api/posts.tsx
import { json } from '@remix-run/node';

export async function loader() {
  const posts = await getPosts();
  return json(posts);
}

// No default export = resource route (no UI)

Meta and Links

import type { MetaFunction, LinksFunction } from '@remix-run/node';

export const meta: MetaFunction<typeof loader> = ({ data }) => {
  return [
    { title: data?.post.title ?? 'Blog' },
    { name: 'description', content: data?.post.excerpt },
  ];
};

export const links: LinksFunction = () => {
  return [
    { rel: 'stylesheet', href: styles },
    { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
  ];
};

Session Management

// utils/session.server.ts
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),
    },
  });
}

Performance

  • Prefetch with <Link prefetch="intent">
  • Use defer for streaming data
  • Implement stale-while-revalidate with headers
  • Code split with dynamic imports
  • Cache loader responses appropriately

Best Practices

  • Always validate form data on the server
  • Use TypeScript for type safety
  • Handle loading and error states
  • Implement proper CSRF protection
  • Use progressive enhancement
  • Test with JavaScript disabled

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

fastapi-python

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

nextjs-react-typescript

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

chrome-extension-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

odoo-development

No summary provided by upstream source.

Repository SourceNeeds Review