We use cookies to understand how the site is used and to display ads. Analytics and advertising only run after you accept. You can change your choice anytime. Privacy policy

Skip to content
devvkit
$devvkit learn --librarie remix-guide

Remix Guide

[react][ssr][full-stack][remix]
JavaScript / TypeScript
Install
npx create-remix@latest
# or: npm create remix@latest my-app

Remix embraces web fundamentals. Instead of client-side data fetching, you export loader and action functions that run on the server. Nested routes load data in parallel, and only the parts of the page that change re-render.

Remix is built on React Router v7. Routes use outlet-based nesting: parent layouts remain mounted when children change. Form handling uses standard HTML forms with progressive enhancement via useFetcher.

Remix v3 (React Router v7) merges the two projects. You get Remix's data patterns with React Router's flexibility. Deploy to Vercel, Cloudflare, Fly.io, or any Node.js host.

Setup

Create project· Scaffold Remix app.
npx create-remix@latest my-app --template remix-run/indie-stack
cd my-app
npm run dev

Routing

Route with loader· Server data loading.
// app/routes/users._index.tsx
export async function loader({ request }: LoaderFunctionArgs) {
  const users = await db.findMany()
  return json({ users })
}

export default function Users() {
  const { users } = useLoaderData<typeof loader>()
  return <div>{users.map(u => <p key={u.id}>{u.name}</p>)}</div>
}
Nested route· Parent layout with outlet.
// app/routes/users.tsx (parent layout)
export default function UsersLayout() {
  return (
    <div>
      <Sidebar />
      <Outlet />
    </div>
  )
}

// app/routes/users..tsx (child)
export async function loader({ params }: LoaderFunctionArgs) {
  const user = await db.findUnique({ where: { id: params.id } })
  return json({ user })
}
Error boundary· Route-level error handling.
export function ErrorBoundary({ error }: { error: Error }) {
  return (
    <div>
      <h1>Oops!</h1>
      <p>{error.message}</p>
    </div>
  )
}
Resource route· Non-page API endpoint.
// app/routes/api.users.ts (no default export)
export async function loader({ request }: LoaderFunctionArgs) {
  const users = await db.findMany()
  return json(users)
}

Actions

Form action· Server mutation.
export async function action({ request }: ActionFunctionArgs) {
  const form = await request.formData()
  const name = form.get('name') as string
  '---'
  await db.user.create({ data: { name } })
  return redirect('/users')
}

export default function NewUser() {
  return (
    <Form method="post">
      <input name="name" />
      <button type="submit">Create</button>
    </Form>
  )
}

Forms

useFetcher· Progressive enhancement.
const fetcher = useFetcher()

<fetcher.Form method="post" action="/users">
  <input name="name" />
  <button type="submit">{fetcher.state === 'submitting' ? 'Saving...' : 'Save'}</button>
</fetcher.Form>