$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>