Remix Framework Guide

Loader (Data Fetching)

// app/routes/users.$id.tsx import { json } from '@remix-run/node'; import { useLoaderData } from '@remix-run/react'; import type { LoaderFunctionArgs } from '@remix-run/node'; export async function loader({ params }: LoaderFunctionArgs) { const user = await getUser(params.id); if (!user) throw new Response('Not Found', { status: 404 }); return json({ user }); } export default function UserPage() { const { user } = useLoaderData<typeof loader>(); return <h1>{user.name}</h1>; }

Action (Form Handling)

import { Form, useActionData } from '@remix-run/react'; import { redirect } from '@remix-run/node'; import type { ActionFunctionArgs } from '@remix-run/node'; export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const name = formData.get('name') as string; if (!name) return json({ error: 'Name required' }, { status: 400 }); await createUser({ name }); return redirect('/users'); } export default function NewUser() { const data = useActionData<typeof action>(); return ( <Form method="post"> <input name="name" type="text" /> {data?.error && <p>{data.error}</p>} <button type="submit">Create</button> </Form> ); }

File-based Routing

app/routes/ ├── _index.tsx # / ├── about.tsx # /about ├── users._index.tsx # /users ├── users.$id.tsx # /users/:id ├── users.$id.edit.tsx # /users/:id/edit └── _layout.tsx # shared layout

Error Boundaries

export function ErrorBoundary() { const error = useRouteError(); if (isRouteErrorResponse(error)) { return <div>{error.status} {error.statusText}</div>; } return <div>Something went wrong</div>; }