Розроблення фронтенду сайту на Remix
Remix — React-фреймворк з наголосом на веб-стандарти, вкладені маршрути та прогресивне поліпшення. Відрізняється від Next.js: немає SSG (тільки SSR та CSR), вкладена маршрутизація з паралельним завантаженням даних, Form Actions на основі Web Forms API.
Вкладена маршрутизація
Remix маршрути можуть бути вкладеними — дочірній маршрут рендеряється всередину батьківського <Outlet />:
app/routes/
├── _layout.tsx → layout для всіх дочірніх
├── _layout.blog.tsx → /blog
├── _layout.blog.$slug.tsx → /blog/:slug
└── _layout.dashboard._index.tsx → /dashboard
// _layout.tsx — батьківський layout
export default function Layout() {
return (
<div>
<Nav />
<main>
<Outlet /> {/* Дочірній маршрут рендеряється тут */}
</main>
</div>
);
}
При навігації Remix завантажує дані дочірнього та батьківського маршрутів паралельно — немає waterfall запитів.
Loaders та Actions
// app/routes/products.$id.tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
// Завантаження даних (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 });
}
// Обробка форми
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">До корзини</button>
</Form>
</div>
);
}
Прогресивне поліпшення
Remix Form працює без JavaScript. <Form> — це звичайний <form> з method="post". З JS — fetch-based оновлення без перезавантаження. Без JS — звичайний POST → redirect → повна сторінка.
Error Boundaries
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <div>HTTP {error.status}: {error.data}</div>;
}
return <div>Щось пішло не так</div>;
}
Error boundary на рівні кожного маршруту. Помилка в дочірному маршруті не ломає батьківський layout.
Оптимістичні оновлення
function LikeButton({ postId, liked }) {
const fetcher = useFetcher();
// Оптимістично показуємо новий стан
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>
);
}
Коли вибирати Remix
- Приложення з тяжкими формами та мутаціями (e-commerce, дашборди)
- Коли важливе прогресивне поліпшення (робота при повільному інтернеті)
- Команди, які переважають веб-стандарти (Request, Response, FormData)
Не підходить: контентні сайти з тисячами сторінок (немає SSG).
Розгортання
Remix через адаптери розгортається на Vercel, Netlify, Cloudflare Workers, Express, Fly.io. @remix-run/cloudflare для edge deployment.
Терміни
Fullstack-приложення на Remix (10–20 маршрутів, форми, авторизація): 2–4 тижня. Складне приложення з вкладеними layouts, real-time, оптимістичними оновленнями: 1–3 місяці.







