Remix全栈开发技能Skill remix

Remix全栈开发技能是专门用于构建现代Web应用程序的专家级工具集,提供React全栈开发、渐进增强、嵌套路由、表单处理、错误边界和边缘部署等核心功能。适用于前端开发、后端API集成、用户认证、数据管理和性能优化等场景,帮助开发者快速构建高性能、可维护的Web应用。关键词:Remix框架、全栈开发、React应用、渐进增强、嵌套路由、表单处理、错误边界、边缘部署、Web开发、现代前端技术。

前端开发 0 次安装 6 次浏览 更新于 2/26/2026

name: remix description: Remix模式,包括加载器、操作、嵌套路由、渐进增强和部署策略。 allowed-tools: 读取、写入、编辑、Bash、Glob、Grep

Remix 技能

用于使用Remix构建全栈应用程序的专家级协助。

能力

  • 实现用于数据获取的加载器
  • 创建用于数据变更的操作
  • 配置带出口的嵌套路由
  • 构建渐进增强的表单
  • 处理错误和边界
  • 为各种平台设置部署

使用场景

在以下情况下调用此技能:

  • 构建全栈React应用程序
  • 实现渐进增强
  • 创建带数据的嵌套布局
  • 正确处理表单提交
  • 部署到边缘平台

输入参数

参数 类型 是否必需 描述
routePath 字符串 路由路径
hasLoader 布尔值 包含加载器
hasAction 布尔值 包含操作
nested 布尔值 具有嵌套路由

路由模式

加载器和操作

// app/routes/users.tsx
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 async function action({ request }: ActionFunctionArgs) {
  await requireUser(request);

  const formData = await request.formData();
  const intent = formData.get('intent');

  if (intent === 'create') {
    const name = formData.get('name') as string;
    const email = formData.get('email') as string;

    if (!name || !email) {
      return json({ error: '姓名和邮箱为必填项' }, { status: 400 });
    }

    await db.user.create({ data: { name, email } });
    return redirect('/users');
  }

  if (intent === 'delete') {
    const id = formData.get('id') as string;
    await db.user.delete({ where: { id } });
    return json({ success: true });
  }

  return json({ error: '无效的操作意图' }, { status: 400 });
}

export default function Users() {
  const { users, search } = useLoaderData<typeof loader>();
  const navigation = useNavigation();

  const isSearching = navigation.state === 'loading' &&
    navigation.location.pathname === '/users';

  return (
    <div>
      <h1>用户管理</h1>

      {/* 搜索表单 - GET请求,渐进增强 */}
      <Form method="get">
        <input
          type="search"
          name="search"
          defaultValue={search}
          placeholder="搜索用户..."
        />
        <button type="submit">搜索</button>
      </Form>

      {/* 创建表单 - POST请求 */}
      <Form method="post">
        <input type="hidden" name="intent" value="create" />
        <input name="name" placeholder="姓名" required />
        <input name="email" type="email" placeholder="邮箱" required />
        <button type="submit">添加用户</button>
      </Form>

      {isSearching ? (
        <p>搜索中...</p>
      ) : (
        <ul>
          {users.map((user) => (
            <li key={user.id}>
              {user.name} - {user.email}
              <Form method="post" style={{ display: 'inline' }}>
                <input type="hidden" name="intent" value="delete" />
                <input type="hidden" name="id" value={user.id} />
                <button type="submit">删除</button>
              </Form>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

嵌套路由

// app/routes/dashboard.tsx
import { Outlet, NavLink } from '@remix-run/react';

export default function Dashboard() {
  return (
    <div className="dashboard">
      <nav>
        <NavLink to="/dashboard" end>概览</NavLink>
        <NavLink to="/dashboard/analytics">分析</NavLink>
        <NavLink to="/dashboard/settings">设置</NavLink>
      </nav>

      <main>
        <Outlet />
      </main>
    </div>
  );
}

// app/routes/dashboard._index.tsx
export default function DashboardIndex() {
  return <h2>仪表板概览</h2>;
}

// app/routes/dashboard.analytics.tsx
export async function loader() {
  const analytics = await getAnalytics();
  return json({ analytics });
}

export default function Analytics() {
  const { analytics } = useLoaderData<typeof loader>();
  return <AnalyticsChart data={analytics} />;
}

错误边界

// app/routes/users.$userId.tsx
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('用户未找到', { 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">
        <h2>{error.status} {error.statusText}</h2>
        <p>{error.data}</p>
      </div>
    );
  }

  return (
    <div className="error">
      <h2>出错了</h2>
      <p>{error instanceof Error ? error.message : '未知错误'}</p>
    </div>
  );
}

乐观UI

// app/routes/todos.tsx
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} />
        <input type="hidden" name="intent" value="toggle" />
        <button type="submit">
          {completed ? '✓' : '○'}
        </button>
      </fetcher.Form>

      <span>{todo.title}</span>

      <fetcher.Form method="post">
        <input type="hidden" name="id" value={todo.id} />
        <input type="hidden" name="intent" value="delete" />
        <button type="submit">×</button>
      </fetcher.Form>
    </li>
  );
}

会话和身份验证

// app/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),
    },
  });
}

export async function getUserId(request: Request) {
  const session = await sessionStorage.getSession(
    request.headers.get('Cookie')
  );
  return session.get('userId');
}

export async function requireUser(request: Request) {
  const userId = await getUserId(request);
  if (!userId) {
    throw redirect('/login');
  }
  return userId;
}

export async function logout(request: Request) {
  const session = await sessionStorage.getSession(
    request.headers.get('Cookie')
  );

  return redirect('/login', {
    headers: {
      'Set-Cookie': await sessionStorage.destroySession(session),
    },
  });
}

最佳实践

  • 使用加载器处理GET请求(数据获取)
  • 使用操作处理POST/PUT/DELETE(数据变更)
  • 利用Form实现渐进增强
  • 使用useFetcher进行非导航变更
  • 实现适当的错误边界

目标流程

  • remix全栈开发
  • 渐进增强
  • 边缘部署
  • 表单处理