Remix Cheat Sheet
Quick reference for Remix — loaders, actions, forms, nested routing, error boundaries, and deployment. Everything you need to build full-stack React apps with Remix.
Project Setup
File-Based Routing
Loaders (GET Data)
Actions (POST/PUT/DELETE)
Forms & Navigation
Error Handling
Meta & Headers
Project Setup
| npx create-remix@latest | Create new Remix project |
| npx remix dev | Start dev server with HMR |
| npx remix build | Build for production |
| npx remix-serve build/server/index.js | Serve production build |
| remix.config.js | Main configuration file |
| npx remix routes | Print all application routes |
File-Based Routing
| app/routes/_index.tsx | → / (root index route) |
| app/routes/about.tsx | → /about |
| app/routes/blog.$slug.tsx | → /blog/:slug (dynamic param) |
| app/routes/blog_.tsx | → /blog (pathless layout escape) |
| app/routes/($lang).about.tsx | → /about or /:lang/about (optional param) |
| app/routes/$.tsx | → /* (splat / catch-all route) |
| app/routes/_auth.login.tsx | → /login (layout group, _auth is pathless) |
| app/routes/blog._index.tsx | → /blog (index of nested layout) |
| app/root.tsx | Root layout (wraps everything) |
Loaders (GET Data)
| export async function loader({ request, params }) { ... } | Server-side data loading (GET) |
| return json({ posts }) | Return JSON response from loader |
| return json({ error }, { status: 404 }) | Return with custom status code |
| return redirect("/login") | Redirect from loader |
| const data = useLoaderData |
Access loader data in component |
| const url = new URL(request.url) | Parse URL/search params in loader |
| url.searchParams.get("q") | Read query parameters |
| throw json({ message: "Not found" }, 404) | Throw response (caught by error boundary) |
Actions (POST/PUT/DELETE)
| export async function action({ request }) { ... } | Handle form submissions (non-GET) |
| const formData = await request.formData() | Parse form data from request |
| formData.get("email") | Get single form field value |
| return json({ errors: { email: "Required" } }) | Return validation errors |
| const actionData = useActionData |
Access action response in component |
| return redirect("/dashboard") | Redirect after successful action |
| const intent = formData.get("intent") | Handle multiple buttons/intents per form |
Error Handling
| export function ErrorBoundary() { ... } | Route-level error boundary |
| const error = useRouteError() | Access caught error |
| isRouteErrorResponse(error) | Check if error is a Response (thrown json/redirect) |
| error.status, error.data | Access Response error status and data |
| export function HydrateFallback() { ... } | Loading UI while route hydrates (SPA mode) |
| export const handle = { breadcrumb: "Blog" } | Route handle for useMatches() |
| const matches = useMatches() | Get all matched routes (breadcrumbs, meta) |
Meta & Headers
| export const meta: MetaFunction = () => [ { title: "Page" } ] | Set page title |
| { name: "description", content: "..." } | Set meta description |
| { property: "og:title", content: "..." } | Set Open Graph meta |
| export function headers({ loaderHeaders }) { ... } | Set HTTP response headers |
| export function links() { return [...] } | Add tags (stylesheets, prefetch) |
| Prefetch on hover/focus intent |
Try It Live
Test these patterns with our free API Tester. No signup needed.
Open API Tester →
Step-by-Step Guide
Read Guide →