Remix Frontend Website Development
Remix is a React framework emphasizing web standards, nested routes, and progressive enhancement. Differs from Next.js: no SSG (only SSR and CSR), nested routing with parallel data loading, Form Actions based on Web Forms API.
Nested routing
Remix routes can be nested — child route renders inside parent's <Outlet />:
app/routes/
├── _layout.tsx → layout for all children
├── _layout.blog.tsx → /blog
├── _layout.blog.$slug.tsx → /blog/:slug
└── _layout.dashboard._index.tsx → /dashboard
// _layout.tsx — parent layout
export default function Layout() {
return (
<div>
<Nav />
<main>
<Outlet /> {/* Child route renders here */}
</main>
</div>
);
}
On navigation Remix loads data for child and parent routes in parallel — no waterfall requests.
Loaders and Actions
// app/routes/products.$id.tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
// Data loading (SSR)
export async function loader({ params }: LoaderFunctionArgs) {
const product = await db.product.findUnique({ where: { id: params.id } });
if (!product) throw new Response('Not Found', { status: 404 });
return json({ product });
}
// Form handling
export async function action({ request, params }: ActionFunctionArgs) {
const formData = await request.formData();
const quantity = Number(formData.get('quantity'));
await addToCart(params.id, quantity);
return redirect('/cart');
}
export default function ProductPage() {
const { product } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
return (
<div>
<h1>{product.name}</h1>
<Form method="post">
<input name="quantity" type="number" defaultValue={1} />
<button type="submit">Add to cart</button>
</Form>
</div>
);
}
Progressive enhancement
Remix Form works without JavaScript. <Form> is a regular <form> with method="post". With JS — fetch-based update without reload. Without JS — regular POST → redirect → full page.
Error Boundaries
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <div>HTTP {error.status}: {error.data}</div>;
}
return <div>Something went wrong</div>;
}
Error boundary at each route level. Error in child route doesn't break parent layout.
Optimistic updates
function LikeButton({ postId, liked }) {
const fetcher = useFetcher();
// Optimistically show new state
const optimisticLiked = fetcher.state !== 'idle'
? !liked
: liked;
return (
<fetcher.Form method="post" action="/api/like">
<input type="hidden" name="postId" value={postId} />
<button>{optimisticLiked ? '❤️' : '🤍'}</button>
</fetcher.Form>
);
}
When to choose Remix
- Applications with heavy forms and mutations (e-commerce, dashboards)
- When progressive enhancement matters (work on slow internet)
- Teams preferring web standards (Request, Response, FormData)
Not suitable: content sites with thousands of pages (no SSG).
Deployment
Remix via adapters deploys to Vercel, Netlify, Cloudflare Workers, Express, Fly.io. @remix-run/cloudflare for edge deployment.
Timeline
Fullstack application on Remix (10–20 routes, forms, authorization): 2–4 weeks. Complex application with nested layouts, real-time, optimistic updates: 1–3 months.







