---
name: remix-data-loader
description: "Remix nested routes, loaders, actions, optimistic UI — the data-mutation patterns React Router 7 keeps. Scaffolds Remix routes with loaders/actions, error boundaries, optimistic UI, and the deferred data patterns for streaming."
license: MIT
---

# Remix Routes & Loaders

Scaffolds the data layer of a Remix (or React Router 7 framework mode) app: file-based nested routes paired with `loader`/`action` functions, error boundaries, and the optimistic-UI and streaming patterns that make Remix navigations feel instant. It's aimed at developers building server-rendered React apps who want correct data-flow wiring — `Form`, `useFetcher`, `defer`, `<Await>` — rather than hand-rolling fetch effects.

## When to use it

- You're starting a new route and want the `loader` + component + `ErrorBoundary` stubbed in one pass.
- A form mutation should update the UI before the server responds (optimistic add/delete, pending counts).
- A page has one fast query and one slow one, and you want the slow part to stream in instead of blocking the whole render.
- You're migrating Remix v2 routes to React Router 7 and want the loader/action conventions preserved.

## What it does

Generates route modules that export the real Remix primitives:

- `loader` with typed `LoaderFunctionArgs`, returning data consumed via `useLoaderData`.
- `action` for `POST`/`PUT`/`DELETE`, paired with `<Form method="post">` or `useFetcher` for inline mutations.
- `ErrorBoundary` using `useRouteError` and `isRouteErrorResponse` for thrown `Response`/`json` errors.
- Optimistic UI through `useFetcher().formData` and `useNavigation` pending states.
- Deferred/streaming data with `defer()`, `<Suspense>`, and `<Await>` resolving promises after the shell ships.

It follows the flat-routes naming convention (`app/routes/posts.$postId.tsx`, `_index.tsx`) and wires `meta`/`headers` exports where relevant.

## Usage

```
"Scaffold a posts.$postId route that loads the post in a loader,
 deletes it in an action, and streams the comments list with defer."
```

Produces, roughly:

```tsx
export async function loader({ params }: LoaderFunctionArgs) {
  const post = await getPost(params.postId); // awaited, blocks shell
  const comments = getComments(params.postId); // promise, not awaited
  return defer({ post, comments });
}

export async function action({ params }: ActionFunctionArgs) {
  await deletePost(params.postId);
  return redirect("/posts");
}
```

## Tips & gotchas

- Only await what the shell needs; pass the rest as bare promises to `defer()`, then wrap them in `<Suspense><Await>`.
- Throw `json(..., { status: 404 })` from a loader to render the nearest `ErrorBoundary` instead of returning null and branching in JSX.
- Use `useFetcher` (not `<Form>`) for mutations that shouldn't trigger navigation — like a "mark done" toggle.
- In React Router 7 framework mode, prefer the generated `Route.LoaderArgs` types over the older `LoaderFunctionArgs` import.

## FAQ

**Does this work with React Router 7?** Yes. The loader/action/`defer` model carried over; the skill emits the matching imports and route-type generics for either target.

**Loader vs. clientLoader?** Loaders run on the server per request; the skill can add a `clientLoader` for client-only data or to cache across navigations.

**How is form data validated?** Actions read `await request.formData()`; the skill leaves a validation slot where you drop Zod or your own checks and return field errors for the component to render.
