Writing Skills
writing-skills
效率与协作技能,来自 obra/superpowers,查看原文获取完整用途和操作说明。
remix【部署与运维】Assists with building full-stack React web applications with Remix v2 and its successor, React Router framework mode. Use when creating apps with nested routing, loader/action patterns, progressive enhancement, or deploying to Node.js, Cloudflare Workers, or other adapters, or when migrating Remix v2 to React Router v7. Trigger words: remix, remix ru…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Remix is a full-stack web framework built on web standards: nested routing, loader/action data patterns, progressive enhancement, and error boundaries that isolate failures to one route segment. Forms work without JavaScript and nested route loaders run in parallel.
Know which "Remix" you are in before writing code:
@remix-run/* packages) is the React framework most existing apps use. Its features were merged into React Router v7 (November 2024) as "framework mode", and the team recommends upgrading. New React projects should start on React Router, not Remix v2.remix package, npx remix new) is a different, new framework: it is not built on React and uses its own component runtime and router. Its release candidate appeared on 2026-08-31 and a stable release was scheduled for 2026-10-02. Its API is not compatible with the loader/action code below.The patterns in this skill (loader, action, Form, fetchers, error boundaries) apply to Remix v2 and React Router framework mode. Check package.json first: @remix-run/* means v2, react-router plus @react-router/dev means framework mode, remix alone means Remix 3.
<Outlet /> for children and pathless layouts for shared UI. In React Router v7 routes are declared in app/routes.ts (flat-file conventions are available through @react-router/fs-routes).loader functions; nested loaders run in parallel, so there is no client-server waterfall. In Remix v2 with single fetch, and in React Router v7, return plain objects (and data() for status codes or headers) rather than json(); defer() is replaced by returning promises, rendered with <Suspense> and <Await>. Use redirect() to redirect.action functions triggered by <Form method="post">; return validation errors with a 400 status and rely on automatic revalidation of every active loader afterwards.useFetcher() for mutations that should not navigate (like buttons, inline edits), useNavigation() for pending state, fetcher.formData for optimistic UI.ErrorBoundary per route and use isRouteErrorResponse() to tell thrown responses (404, 403) from unexpected errors.createCookieSessionStorage() with a secrets array from the environment; redirect from the loader when unauthenticated. Remix has no built-in CSRF protection: validate the Origin header or add a token for state-changing actions.type { Route } from "./+types/<module>" and annotate Route.LoaderArgs, Route.ActionArgs, Route.ComponentProps; run react-router typegen (also part of npm run typecheck).@remix-run/node, @remix-run/cloudflare, @remix-run/deno. React Router v7: @react-router/node, @react-router/cloudflare, @react-router/serve.npx codemod remix/2/react-router/upgrade && npm install
npm run typecheck && npm run build
@remix-run/react to react-router, @remix-run/node to @react-router/node, @remix-run/dev to @react-router/dev.react-router dev, react-router build, react-router-serve build/server/index.js.app/routes.ts and react-router.config.ts (ssr: true), swap remix() for reactRouter() from @react-router/dev/vite, rename RemixServer to ServerRouter and RemixBrowser to HydratedRouter (from react-router/dom), and add .react-router/ to .gitignore.User request: "Create a route that lists tasks and lets me add one with a form that works without JavaScript."
// app/routes/tasks.tsx (React Router framework mode)
import { data, Form } from "react-router";
import type { Route } from "./+types/tasks";
import { createTask, listTasks } from "~/models/task.server";
export async function loader() {
return { tasks: await listTasks() };
}
export async function action({ request }: Route.ActionArgs) {
const form = await request.formData();
const title = String(form.get("title") ?? "").trim();
if (!title) return data({ error: "Title is required" }, { status: 400 });
await createTask({ title });
return { ok: true };
}
export default function Tasks({ loaderData, actionData }: Route.ComponentProps) {
return (
<>
<ul>{loaderData.tasks.map((t) => <li key={t.id}>{t.title}</li>)}</ul>
<Form method="post">
<input name="title" />
{actionData && "error" in actionData && <p>{actionData.error}</p>}
<button type="submit">Add task</button>
</Form>
</>
);
}
Result: submitting with JavaScript disabled does a normal POST and re-renders the list; with JavaScript it submits via fetch and revalidates the loader without a full reload.
User request: "Move our Remix 2 app to React Router 7."
Run the codemod and the commands from the migration steps. A typical leftover fix, a like button that must not navigate:
const fetcher = useFetcher();
<fetcher.Form method="post" action="/posts/42/like">
<button type="submit">{fetcher.formData ? "Liked" : "Like"}</button>
</fetcher.Form>
Result: imports come from react-router, scripts call react-router, and remaining errors are mostly json()/defer() calls to replace with plain returns and useLoaderData call sites to move to loaderData props (or keep the hook).
loader, not useEffect plus fetch.<Form> rather than <form onSubmit> so the page works before hydration.ErrorBoundary on every route that can fail so one child does not take the page down.*.server.ts files; the build removes loader and action from client bundles, but imports they share do not get removed unless they are server modules.@remix-run/* with react-router packages; pick one.