Remix is the React framework that embraces web standards. Forms, cookies, headers, streams — everything uses the platform APIs you already know.
Loaders: Server-Side Data
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
export async function loader({ request }) {
const url = new URL(request.url);
const search = url.searchParams.get("q");
const products = await db.product.findMany({
where: search ? { title: { contains: search } } : {},
take: 20,
});
return json({ products, search });
}
export default function Products() {
const { products, search } = useLoaderData();
return (
<Form method="get">
<input name="q" defaultValue={search} />
{products.map(p => <div key={p.id}>{p.title}</div>)}
</Form>
);
}
Actions: Mutations via Forms
export async function action({ request }) {
const formData = await request.formData();
await db.product.create({
data: {
title: formData.get("title"),
price: Number(formData.get("price")),
},
});
return redirect("/products");
}
No useState. No useEffect. No client-side state management. Just forms.
Nested Routes: Parallel Loading
Routes are files. Nested routes load data in parallel:
app/routes/
_index.tsx
products.tsx (layout)
products._index.tsx (list)
products.$id.tsx (detail)
Streaming with defer
import { defer } from "@remix-run/node";
import { Await } from "@remix-run/react";
export async function loader() {
return defer({
critical: await db.product.findFirst(),
slow: db.analytics.getReport(), // Not awaited!
});
}
Error Boundaries per Route
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <div>{error.status}: {error.statusText}</div>;
}
return <div>Something went wrong</div>;
}
Build data-driven Remix apps? My Apify tools feed scraped data into your loaders.
Custom solution? Email spinov001@gmail.com
Top comments (0)