Migrating from Remix and React Router

These tables map the form and mutation features of Remix and React Router to Silgi. They cover forms and handlers only; the tables on pages, data and the server in Migrating from Nuxt apply to any app. none means Silgi has no equivalent today.

The shape is one thing in Silgi: a handler, a typed function that runs on the server, exported from server/index.ts. A form posts to it (Form action={fn}), a component calls it (useAction(fn), useAsyncData(fn, input)), and with a route a URL answers it. It works without JavaScript, it is a public endpoint that must check who calls it, and it needs no directive.

The route types (+types, href) and Vite's import.meta.env and modes are in Types and environment.

Remix, React RouterSilgiDifferences
export async function action({ request }) in a routea handler exported from server/index.tsthe page posts to it with <Form action={fn}>
await request.formData()input of the handlerdecoded by formToObject
return data({ errors }, { status: 400 })return fail(400, { errors })
redirect("/x")redirect("/x")
<Form method="post" action reloadDocument replace><Form action={fn} reloadDocument replace>reset, instance, refresh and restore are added; Form posts through a handler or navigates with GET
useActionData()useAction(fn).state, .failure, .errorreadable anywhere in the app, not only in the route
useNavigation()useNavigation()state is idle or loading, plus revalidation; the fields of a running submission are in pendingRuns(app)
useFetcher() with fetcher.Form, submit, load, state, data, formDatauseAction(fn, { instance }) and <Form action={fn} instance={key}>a keyed run that does not navigate; useForm(fn).for(key) reads one
useFetchers()pendingRuns(app) from silgi/adapter
useSubmit()useAction(fn).run(input)a JSON call, not a form post
useRevalidator()useRouter().refresh() and useNavigation().revalidation
shouldRevalidate({ defaultShouldRevalidate })export const shouldRefresh in the pagedefaultShouldRefresh for defaultShouldRevalidate, plus currentUrl and nextUrl
optimistic UI from fetcher.formDatauseOptimistic(value, reduce)pending lists the running calls, each with its formData
a hidden intent field and a switch in actionone handler per intent
clientAction, clientLoadernonehandlers run on the server; browser-only data is useAsyncData(key, fn, { server: false })
unstable_parseMultipartFormDataform: { onFile }
resource routesa handler with a route; raw: true for a plain endpoint
loader and useLoaderDatauseAsyncData(fn, input), or x.load.ts and useData()load for a status decided before render
useFormActionnoneForm resolves its own action