К основному содержанию
правило

remix — правило для Cursor: стандарты Remix-приложений

Правило для Cursor задаёт структуру Remix-проекта: логика маршрута и UI в одной папке, мутации через Form, тяжёлые запросы в loader. Пригодится, когда агент разносит код по разным файлам.

Установить в Cursor · Вручную: положить файлы Все способы и платформы ↓
.cursor/rules/remix.mdc
Открыть на GitHub

Что делает remix

Правило описывает, как устроен Remix поверх React Router v7, и требует держать логику маршрута рядом с компонентом. Каждый маршрут живёт в своей папке: route.tsx, loader.ts, action.ts, при необходимости meta.ts и error-boundary.tsx. Вместо клиентских запросов и проброса пропсов — прямой вызов серверной функции внутри loader.

Дальше идут соглашения по структуре: папки app/components, app/features, app/hooks, app/services, app/utils. Компоненты и типы — PascalCase, имена файлов — kebab-case. Мутации только через Remix Form, чтобы работала встроенная ревалидация. Для эндпоинтов без UI — resource routes.

Отдельный блок про производительность и грабли. Тяжёлое получение данных — в loader и action, потому что они выполняются на сервере и не раздувают клиентский бандл. Медленные данные отдаются через defer и Await с Suspense. А вот window, localStorage и document внутри loader или action не сработают: эти функции крутятся на сервере, так что токен придётся передавать через cookie, заголовки или form data. Правило подключается файлом .cursor/rules/remix.mdc и действует на **/*.{js,jsx,ts,tsx}.

Какие задачи решает

  • разложить маршрут Remix по папке с route.tsx, loader.ts и action.ts
  • перевести мутацию с клиентского fetch на Remix Form с ревалидацией
  • вынести тяжёлый запрос из useEffect в серверный loader
  • отдать медленные данные через defer и Await, не блокируя первый рендер
  • поймать обращение к window или localStorage в серверном коде

Примеры запросов агенту

  • Разложи маршрут posts.$postId по папке: route.tsx, loader.ts, error-boundary.tsx
  • Перепиши форму создания поста на Remix Form с action и редиректом на /posts
  • Вынеси загрузку данных дашборда в loader и отдай медленную часть через defer

Как установить remix

Вручную: положить файлы

.cursor/rules/remix.mdc

Во frontmatter: description, globs, alwaysApply

По шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules

Вопросы о remix

Как установить правило remix в Cursor?

Создайте файл .cursor/rules/remix.mdc в проекте и вставьте в него содержимое правила. Оно подхватится для файлов по маске **/*.{js,jsx,ts,tsx}.

Что делает правило remix?

Задаёт агенту стандарты Remix: совместное размещение логики маршрута и UI, мутации через Form, серверные loader и action, отложенная загрузка данных через defer.

Почему в loader нельзя обращаться к window?

loader и action выполняются на сервере, где window, localStorage и document недоступны. Нужные данные передавайте через cookie, заголовки или form data.

Исходный текст

remix.mdc · CC0-1.0
---
description: This guide outlines definitive best practices for building robust, performant, and maintainable applications with Remix, emphasizing co-location, type safety, and efficient data handling.
globs: **/*.{js,jsx,ts,tsx}
---
# Remix Best Practices

Remix provides a powerful full-stack framework built on React Router v7. Adhering to these guidelines ensures a consistent, high-quality codebase that leverages Remix's strengths for server-rendered applications.

## 1. Code Organization and Structure

**Always co-locate route logic and UI.** Each route must live in its own folder, containing its `route.tsx` component, `loader.ts`, `action.ts`, and optional `meta.ts` or `error-boundary.tsx`. This keeps related concerns together and simplifies navigation.

❌ BAD:
```tsx
// app/data/posts.ts
export async function getPost(id: string) { /* ... */ }

// app/components/PostDetail.tsx
import { getPost } from '~/data/posts';
function PostDetail() {
  // Client-side fetch or prop drilling
}

// app/routes/posts.$postId.tsx
import PostDetail from '~/components/PostDetail';
export default function PostRoute() {
  return <PostDetail />;
}
```

✅ GOOD:
```tsx
// app/routes/posts.$postId/route.tsx
import { json, useLoaderData } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await db.getPost(params.postId); // Direct server call
  if (!post) throw new Response("Not Found", { status: 404 });
  return json({ post });
}

export default function PostDetailRoute() {
  const { post } = useLoaderData<typeof loader>();
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

// app/routes/posts.$postId/error-boundary.tsx
import { isRouteErrorResponse, useRouteError } from "@remix-run/react";
export function ErrorBoundary() {
  const error = useRouteError();
  if (isRouteErrorResponse(error)) {
    return <div>Error: {error.status} {error.statusText}</div>;
  }
  return <div>Unknown Error!</div>;
}
```

**Maintain a clear `src` directory hierarchy.** Separate concerns into logical folders like `app/components`, `app/features`, `app/hooks`, `app/utils`, `app/services`, etc.

❌ BAD:
```
app/
├── index.tsx
├── post-list.tsx
├── user-profile.tsx
├── api.ts
└── helpers.ts
```

✅ GOOD:
```
app/
├── components/       // Reusable UI components
├── features/         // Domain-specific components/logic
├── hooks/            // Custom React hooks
├── routes/           // File-based routes (as above)
├── services/         // Database/API interaction logic
├── utils/            // Generic utility functions
└── entry.client.tsx
└── entry.server.tsx
```

**Enforce consistent naming conventions.** Use PascalCase for React components and types, and kebab-case for file names.

❌ BAD: `mycomponent.jsx`, `MyComponent.ts`, `userProfile.tsx`
✅ GOOD: `MyComponent.tsx`, `user-profile.ts`, `UserProfile.tsx`

## 2. Common Patterns and Anti-patterns

**Use Remix `Form` for all data mutations.** This leverages Remix's built-in revalidation and pending UI states for a robust user experience.

❌ BAD:
```tsx
import { useState } from 'react';
function NewPostForm() {
  const [title, setTitle] = useState('');
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await fetch('/posts', { method: 'POST', body: JSON.stringify({ title }) });
  };
  return <form onSubmit={handleSubmit}>...</form>;
}
```

✅ GOOD:
```tsx
// app/routes/posts.new/route.tsx
import { Form, redirect } from "@remix-run/react";
import type { ActionFunctionArgs } from "@remix-run/node";

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const title = formData.get("title");
  await db.createPost({ title });
  return redirect("/posts");
}

export default function NewPostForm() {
  return (
    <Form method="post">
      <input type="text" name="title" />
      <button type="submit">Create Post</button>
    </Form>
  );
}
```

**Utilize Resource Routes for API-only endpoints.** For data fetching or mutations not tied to a specific UI page, create a resource route.

❌ BAD: Creating a separate API server or `app/api/posts.ts` that isn't a route.
✅ GOOD:
```tsx
// app/routes/api.posts.$postId.ts
import { json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await db.getPost(params.postId);
  return json(post);
}
```

## 3. Performance Considerations

**Perform all heavy data fetching in `loader` and `action` functions.** These run exclusively on the server, keeping your client bundle small and fast.

❌ BAD:
```tsx
// app/routes/dashboard.tsx
import { useEffect, useState } from 'react';
function Dashboard() {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch('/api/dashboard-data').then(res => res.json()).then(setData);
  }, []);
  return <div>{data ? 'Loaded' : 'Loading...'}</div>;
}
```

✅ GOOD:
```tsx
// app/routes/dashboard.tsx
import { json, useLoaderData } from "@remix-run/node";
export async function loader() {
  const data = await db.getDashboardData(); // Server-side data fetch
  return json(data);
}
export default function Dashboard() {
  const data = useLoaderData<typeof loader>();
  return <div>{data ? 'Loaded' : 'Loading...'}</div>;
}
```

**Defer non-critical data with `defer` and `Await` for faster initial page loads.** This allows critical data to render immediately while slower data streams in.

❌ BAD: Waiting for all data to resolve before rendering.
```tsx
// loader that waits for everything
export async function loader() {
  const critical = await getCriticalData();
  const slow = await getSlowData(); // Blocks initial render
  return json({ critical, slow });
}
```

✅ GOOD:
```tsx
// loader that streams slow data
import { defer, Await, json } from "@remix-run/node";
import { Suspense } from "react";

export async function loader() {
  const critical = await getCriticalData();
  const slowPromise = getSlowData(); // Returns a promise
  return defer({ critical, slow: slowPromise });
}

export default function MyRoute() {
  const { critical, slow } = useLoaderData<typeof loader>();
  return (
    <div>
      <h1>Critical: {critical.message}</h1>
      <Suspense fallback={<p>Loading slow data...</p>}>
        <Await resolve={slow}>
          {(resolvedSlow) => <p>Slow: {resolvedSlow.message}</p>}
        </Await>
      </Suspense>
    </div>
  );
}
```

## 4. Common Pitfalls and Gotchas

**Avoid client-side specific APIs in `loader` or `action` functions.** These functions run on the server, so `window`, `localStorage`, `document`, etc., are unavailable.

❌ BAD:
```tsx
export async function loader() {
  const token = window.localStorage.getItem('token'); // Will fail on server
  // ...
}
```

✅ GOOD: Pass necessary data via cookies, headers, or form data.
```tsx
export async function loader({ request }: LoaderFunctionArgs) {
  const cookieHeader = request.headers.get("Cookie");
  const token = getAuthTokenFromCookie(cookieHeader); // Server-safe
  // ...
}
```

**Treat `useLoaderData` and `useActionData` as immutable.** Remix revalidates data, so direct mutations won't persist and can lead to unexpected behavior.

❌ BAD:
```tsx
const { items } = useLoaderData<typeof loader>();
items.push("new item"); // Don't mutate loader data directly
```

✅ GOOD: Create a new array or object if you need to modify data client-side.
```tsx
const { items } = useLoaderData<typeof loader>();
const [clientItems, setClientItems] = useState(items);
// ... setClientItems([...clientItems, "new item"])
```

## 5. Hooks Best Practices

**Use `useNavigation` for pending UI states.** This provides a seamless user experience during transitions.

❌ BAD: Manually managing `isLoading` state with `useState` for form submissions.
✅ GOOD:
```tsx
import { Form, useNavigation } from "@remix-run/react";
export default function MyForm() {
  const navigation = useNavigation();
  const isSubmitting = navigation.state === "submitting";
  return (
    <Form method="post">
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Saving..." : "Save"}
      </button>
    </Form>
  );
}
```

**Use `useFetcher` for non-navigation data mutations or fetches.** This is ideal for background updates, like liking a post, without triggering a full page navigation.

❌ BAD: Using `<Form>` for every small interaction that doesn't need a redirect.
✅ GOOD:
```tsx
import { useFetcher } from "@remix-run/react";
export default function LikeButton({ postId }: { postId: string }) {
  const fetcher = useFetcher();
  const isLiking = fetcher.state === "submitting";
  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>
  );
}
```

## 6. Type Safety with TypeScript

**Always use TypeScript with Remix.** Leverage `typeof loader` and `typeof action` for type-safe data access, ensuring robust code and improved developer experience.

❌ BAD:
```tsx
// No types, relies on runtime checks
function MyComponent() {
  const data = useLoaderData(); // data is 'any'
  // ...
}
```

✅ GOOD:
```tsx
import { json, useLoaderData } from "@remix-run/node";
export async function loader() {
  return json({ message: "Hello", count: 42 });
}
export default function MyComponent() {
  const data = useLoaderData<typeof loader>(); // data is { message: string; count: number; }
  return <p>{data.message} {data.count}</p>;
}
```

## 7. Security

**Implement rate-limiting for critical endpoints.** Protect your `action` and `resource` routes from abuse. This is typically done at the edge (Cloudflare, Vercel) or with server-side middleware.

❌ BAD: No protection for public-facing mutation endpoints.
✅ GOOD:
```ts
// app/routes/signup.ts
import { json, ActionFunctionArgs } from "@remix-run/node";
import { rateLimit } from "~/utils/rate-limiter.server"; // Custom server utility

export async function action({ request }: ActionFunctionArgs) {
  await rateLimit(request, "signup", { limit: 5, window: 60 }); // 5 requests per minute
  // ... signup logic
  return json({ success: true });
}
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Похожие правила IDE

Все правила IDE по теме «Фронтенд и UI» →
shopify-apps sickn33 Набор шаблонов для разработки приложений Shopify на Remix и React Router. Помогает настроить App Bridge, интерфейс на Polaris, биллинг и обработку вебхуков. CX Codex +1 1 000установок Shopify Expert официальный github Пишет код тем на Liquid, настраивает блоки Online Store 2.0 и проектирует приложения на Remix. Пригодится при вёрстке витрин и работе с Shopify GraphQL API. ★ 40 451звёзд репо tauri-development Mindrally Скилл задаёт правила разработки десктопных программ на Tauri. Он настраивает связку Rust-бэкенда с фронтендом на TypeScript, React или Next.js. CX Codex +1 1 300установок nextjs-typescript-tailwindcss-supabase Mindrally Скилл задаёт агенту правила для Next.js 14, TypeScript, TailwindCSS и Supabase: серверные компоненты, обработка ошибок, читаемый код. CX Codex +1 1 300установок sales-app-extensibility vtex Помогает разрабатывать, настраивать и собирать расширения для VTEX Sales App. Проводит через семь этапов от проверки структуры проекта до деплоя. CX Codex +1 856установок inngest-realtime inngest Скилл учит агента публиковать обновления из durable-функций Inngest в живой интерфейс: статусы заказов, стриминг токенов, логи и подтверждения от пользователя. CX Codex +1 797установок

Из того же набора79

scrapy Правило с набором практик для Scrapy: структура проекта, items, pipelines, middlewares. Пригодится, когда агент пишет парсеры на Python. ★ 3 579звёзд репо scikit-image Правило задаёт команде единый стиль работы с scikit-image: неизменяемость входных массивов, float-диапазон [0, 1], векторизация вместо циклов по пикселям. ★ 3 579звёзд репо ruby Правило для Cursor задаёт команде единый стиль Ruby: RuboCop, соглашения Rails, работа с N+1 и защита от SQL-инъекций. ★ 3 579звёзд репо riverpod Правило для Cursor задаёт команде единый стиль работы с Riverpod в Flutter: где держать провайдеры, когда ref.watch, а когда ref.read. ★ 3 579звёзд репо react Правило для Cursor задаёт команде единые стандарты React: чистые компоненты, правила хуков, структура по фичам. Пригодится, когда в проекте несколько разработчиков и ИИ-агент пишет код вразнобой. ★ 3 579звёзд репо react-native Правило задаёт обязательные практики для React Native: feature-first структуру, функциональные компоненты, хуки и типизацию. Пригодится, когда проект растёт и файлы расползаются. ★ 3 579звёзд репо qwik Правило Cursor, которое подсказывает агенту, как писать Qwik-приложения: component$, useStore, QRL с суффиксом $ и загрузка данных через loader$. ★ 3 579звёзд репо pytest Правило задаёт команде единый стиль тестов на pytest: структуру проекта, фикстуры, параметризацию и моки. Пригодится, когда тесты пишут несколько человек. ★ 3 579звёзд репо

Спонсоры проекта

Компании, которые помогают держать аналитику открытой