本文へ移動
cccskills
無料GitHub で公開

remix-v2-data-flow

Remix v2 data loading and mutations. Use when writing loaders, actions, deferred data, revalidation logic, or pending state. Triggers on loader, action, useLoaderData, useActionData, json(), defer(), <Await>, shouldRevalidate, useRevalidator, useNavigation, useTransition (v1 holdover).

インストール方法を見る

含まれるファイル(5)

  • SKILL.md10.8 KB
  • references/actions.md8.6 KB
  • references/defer-await.md7.0 KB
  • references/loaders.md8.3 KB
  • references/revalidation.md8.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Remix v2 Data Flow

Quick Reference

Loader + typed read:

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

export async function loader({ request }: LoaderFunctionArgs) {
  const invoices = await db.invoice.findMany();
  return json({ invoices });
}

export default function Invoices() {
  // typeof loader is a type ANNOTATION (not assertion) — drives SerializeFrom<T>.
  const { invoices } = useLoaderData<typeof loader>();
  return <InvoiceList invoices={invoices} />;
}

Action + redirect-after-success (PRG):

import { json, redirect, type ActionFunctionArgs } from "@remix-run/node";
import { useActionData, Form } from "@remix-run/react";

export async function action({ request }: ActionFunctionArgs) {
  const form = await request.formData();
  const parsed = NewProject.safeParse(Object.fromEntries(form));
  if (!parsed.success) return json({ errors: parsed.error.flatten().fieldErrors }, { status: 400 });
  const project = await db.project.create({ data: parsed.data });
  return redirect(`/projects/${project.id}`);
}

Canonical APIs

Route modules export loader / action; components read results via useLoaderData<typeof loader>() and useActionData<typeof action>(). After every action, Remix automatically revalidates the loaders of all matching routes on the page, so the UI stays consistent with the server without manual cache invalidation.

Signatures:

  • loader: ({ request, params, context }: LoaderFunctionArgs) => Response | Promise<Response> — server-only read, runs on SSR and on client navigations via fetch.
  • action: ({ request, params, context }: ActionFunctionArgs) => Response | Promise<Response> — server-only handler for non-GET requests (POST/PUT/PATCH/DELETE).
  • json(data, init?: number | ResponseInit): TypedResponse<typeof data> — ergonomic JSON Response wrapper with status/headers.
  • redirect(url, init?: number | ResponseInit): TypedResponse<never> — 30x response; default 302.

Imports: @remix-run/node for server utilities (json, redirect, defer, type args) on Node; substitute @remix-run/cloudflare or @remix-run/deno for those targets. Hooks and components come from @remix-run/react.

Type Annotations, Not Assertions

useLoaderData<typeof loader>() is a type annotation, not a as-style assertion. The generic feeds SerializeFrom<typeof loader>, which models the wire-format transformation: Date becomes string, Map/Set collapse, undefined fields are stripped, class methods vanish. If you call data.createdAt.getFullYear() on a Date field, that's a runtime bug — the type already says string.

When json() Is Optional in v2

v2 did not change the underlying contract: loaders and actions must return a Response. json() is the ergonomic wrapper that sets application/json and lets you supply status / headers. Bare object returns work in v2 (Remix auto-wraps as json()), but json() is preferred for explicit status, headers, and clean TypedResponse<T> typing. Reach for json() whenever you need:

  • A non-200 status code (e.g. { status: 400 } for validation errors).
  • Custom headers (caching, Set-Cookie).
  • An explicit TypedResponse<T> for clean useLoaderData<typeof loader>() inference.

Throwing for Short-Circuits

Throwing a Response from a loader or action exits the data function immediately. Use this for auth guards (throw redirect("/login")) and 404s (throw new Response("Not Found", { status: 404 }) or throw json({ message }, { status: 404 })). Throwing a plain Error will not be classified as a route response by useRouteError() / isRouteErrorResponse().

Streaming Rejections: <Await errorElement> + useAsyncError()

When a promise passed through defer() rejects, an <Await errorElement={...}> boundary catches it inline — without it, the rejection bubbles to the route's ErrorBoundary and tears down the whole page, defeating the streaming benefit. Inside the errorElement, call useAsyncError() (from @remix-run/react) to read the rejection value — this is the streaming analogue of useRouteError().

function ReviewsError() {
  const error = useAsyncError(); // typed as `unknown`
  return <p>Failed to load reviews: {String(error)}</p>;
}

<Suspense fallback={<ReviewsSkeleton />}>
  <Await resolve={reviews} errorElement={<ReviewsError />}>
    {(r) => <ReviewList reviews={r} />}
  </Await>
</Suspense>

Sensitive Data

Everything returned from a loader travels to the browser as JSON. Project to a safe DTO ({ id, email, name }) before returning; never return the full Prisma User, password hashes, API keys, or internal flags. Loaders execute server-only — but the return value is shipped to the client wholesale.

Mutations Belong in Actions

Loaders run on every GET navigation and may be invoked speculatively by prefetch; they also re-run during automatic revalidation. Anything that mutates persistent state must live in action, reached via <Form method="post"> or useFetcher. Calling fetch() directly from a component to hit a Remix route bypasses revalidation, pending state, and progressive enhancement — use useFetcher().submit() / useFetcher().load() instead.

Routing Gotchas to Remember

  • Only the deepest matching action runs. Index routes nested under a layout collide unless you target them with <Form action="/things?index" method="post" />.
  • useActionData is scoped to the current route. It cannot access action results from parent or child routes; to share, lift the action or use a useFetcher with key.
  • Headers from the leaf loader win. Remix only uses the deepest matching headers export. Parent caching policies are ignored unless you explicitly merge parentHeaders.
  • Default revalidation revalidates ALL routes on the page after an action — even those whose params didn't change. Use shouldRevalidate to opt out of expensive parent loaders.

Pending State (v1 → v2)

useTransition is removed in v2 — use useNavigation. The submission object is flattened directly onto the navigation in v2 (and the fetcher likewise; both nav.formData/nav.formMethod and fetcher.formData/fetcher.formMethod are flat). formMethod is now UPPERCASE in v2 ("POST", not "post"); comparisons like nav.formMethod === "post" silently never match. fetcher.type is also gone — branch on fetcher.state plus presence of fetcher.formData.

GET submissions go idle → loading → idle. POST flow goes idle → submitting → loading → idle. Spinners gated only on "submitting" will miss GET forms. GET submissions still populate nav.formData and nav.formMethod === "GET" during the loading phase, so filter-form pending UI should branch on formData presence, not on state === 'submitting'. For useFetcher, submitting applies to BOTH GET (<fetcher.Form method='get'> and fetcher.submit(..., {method:'get'})) and non-GET; only fetcher.load() skips submitting. This is the inverse of useNavigation, which skips submitting for GET.

Gates (decision sequencing)

Answer in order. Pass means the condition is true; pick the API on the same line and stop.

loader vs useEffect

  1. Is the data needed for correct first render of this route (SSR, prefetch, automatic revalidation after actions)?
    • Pass → loader + useLoaderData<typeof loader>(). Stop.
    • Fail → Step 2.
  2. Is the fetch driven by post-mount user interaction, timer, or subscription (not route entry)?
    • Pass → useEffect / event handlers. Stop.
    • Fail → Prefer loader + revalidation; do not mirror navigation inside an effect.

json() vs raw Response vs defer()

  1. Do any returned fields need to stream (slow query, expensive aggregation) while the page renders fast?
    • Pass → defer({ critical: await…, slow: promiseWithoutAwait }) + <Suspense><Await>…</Await></Suspense>. Stop.
    • Fail → Step 2.
  2. Do you need a custom status code, custom headers, or explicit TypedResponse<T> typing?
    • Pass → json(data, init) (or redirect(url, init) for 3xx). Stop.
    • Fail → Step 3.
  3. Do you need a non-JSON body (binary, plain text, streamed file)?
    • Pass → Build a raw new Response(body, init). Stop.
    • Fail → Default to json(data) — it's the documented v2 contract for object payloads.

<Form> / route action vs useFetcher

  1. Should the URL or history stack change (bookmark / share / back returns to prior screen)?
    • Pass → <Form method="post"> posting to a route action. Stop.
    • Fail → Step 2.
  2. Mutation stays on the same route (inline edit, list-row toggle, popover, optimistic UI)?
    • Pass → useFetcher() / fetcher.Form / fetcher.submit(). Stop.

Additional Documentation

  • Loaders: See references/loaders.md for loader signature, typed useLoaderData, json() vs raw Response, redirect(), throwing, sensitive-data filtering, params handling.
  • Actions: See references/actions.md for action signature, FormData parsing, useActionData<typeof action>(), zod/valibot validation, redirect-after-success.
  • Defer & Await: See references/defer-await.md for defer() + <Await> + <Suspense>, when streaming helps TTFB, error handling.
  • Revalidation & Pending State: See references/revalidation.md for automatic revalidation, shouldRevalidate, useRevalidator, useNavigation (plus v1 useTransition rename).

v1 → v2 Quick Diff

Concernv1v2
Navigation hookuseTransition()useNavigation()
Submission shapetransition.submission.formMethodflat nav.formMethod / nav.formData
formMethod casing"post""POST" (UPPERCASE)
Fetcher type fieldfetcher.type === "actionSubmission"branch on fetcher.state + fetcher.formData
Loader args typeLoaderArgs / ActionArgsLoaderFunctionArgs / ActionFunctionArgs
Returning datajson(data) requiredjson(data) still the documented contract

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use when you need to mine a conversation, session transcript, or design discussion for architectural decisions before writing ADRs. Identifies problem-solution pairs, trade-off debates, technology choices, and explicit "[ADR]" tags. Triggers on "what decisions did we make", "extract decisions from this chat", "find the choices in our discussion", or "summarize architectural decisions". Also useful after long planning sessions to capture decisions that were made implicitly. Does NOT write ADR documents — use adr-writing or write-adr for that.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when writing or formatting an ADR document using the MADR template, applying Definition of Done (E.C.A.D.R.) criteria, or verifying ADR completeness. Triggers on "write the ADR", "format as MADR", "check ADR quality", "mark gaps in ADR". Also triggers when a decision has been extracted and needs to become a document. Does NOT extract decisions from conversations (use adr-decision-extraction) or orchestrate the full extract-confirm-write workflow (use write-adr).

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when auditing an agent codebase against the 12-Factor Agents methodology, reviewing LLM-powered system architecture, or assessing agentic app compliance. Triggers on "analyze agent architecture", "12-factor audit", "how compliant is this agent", or "evaluate this LLM app". Also applies when comparing frameworks or planning agent improvements. Not for quick checklists — this performs deep per-factor codebase analysis with file-level evidence.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Vercel AI Elements for workflow UI components. Use when building chat interfaces, displaying tool execution, showing reasoning/thinking, or creating job queues. Triggers on ai-elements, Queue, Confirmation, Tool, Reasoning, Shimmer, Loader, Message, Conversation, PromptInput.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Reviews App Intents code for intent structure, entities, shortcuts, and parameters. Use when reviewing code with import AppIntents, @AppIntent, AppEntity, AppShortcutsProvider, or @Parameter.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when the user wants a cited, structured read of local documents and project knowledge. Triggers on: "analyze these docs", "scan my project for context", "read the docs folder", "summarize what's in .beagle/concepts/", "extract context from docs/", "what's in this folder", "go read everything in X and tell me what's there". Also invoked programmatically by other beagle skills (prfaq-beagle Ignition, brainstorm-beagle reference points, strategy-interview context grounding) via the companion contract. Does NOT trigger on codebase lookups ("find this function", "search the repo"), web research (use web-research), LLM-as-judge evaluation (use llm-judge), or document editing (use humanize-beagle). Produces a written scan plan, parallel-subagent findings, and a cited synthesis report on disk — never inline prose, never unsourced claims.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

existential-birds のスキルをすべて見る

このスキルの問題を報告する