Visão Geral do Remix v2
Remix v2 é um framework React full-stack que aproveita as tecnologias fundamentais da web (HTTP, formulários, cache do navegador). O v2 traz integração com Vite, melhor segurança de tipos e melhorias de performance.
Integração com Vite
Configuração
// vite.config.ts
import { vitePlugin as remix } from '@remix-run/dev';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
remix({
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true
}
})
]
});
Servidor de Desenvolvimento
npm run dev
# HMR rápido do Vite disponível
Roteamento Baseado em Arquivos
app/
├── routes/
│ ├── _index.tsx # /
│ ├── about.tsx # /about
│ ├── posts._index.tsx # /posts
│ ├── posts.$postId.tsx # /posts/:postId
│ ├── posts.$postId_.edit.tsx # /posts/:postId/edit
│ └── ($lang).docs.$.tsx # /:lang?/docs/*
└── root.tsx
Convenções de Nomenclatura de Rotas
| Padrão | Significado |
|---|---|
| _index | Rota índice |
| $param | Parâmetro dinâmico |
| ($param) | Parâmetro opcional |
| $ | Splat (catch-all) |
| _ | Rota sem layout |
| . | URL aninhada |
Carregamento de Dados
loader
// routes/posts.$postId.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
export async function loader({ params }: LoaderFunctionArgs) {
const post = await db.post.findUnique({
where: { id: params.postId }
});
if (!post) {
throw new Response('Não encontrado', { status: 404 });
}
return json({ post });
}
export default function Post() {
const { post } = useLoaderData<typeof loader>();
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
action
// routes/posts.new.tsx
import { redirect, type ActionFunctionArgs } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
import { z } from 'zod';
const schema = z.object({
title: z.string().min(1),
content: z.string().min(10)
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const result = schema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return json({ errors: result.error.flatten() }, { status: 400 });
}
const post = await db.post.create({ data: result.data });
return redirect(`/posts/${post.id}`);
}
export default function NewPost() {
const actionData = useActionData<typeof action>();
return (
<Form method="post">
<input name="title" />
{actionData?.errors?.fieldErrors?.title && (
<span>{actionData.errors.fieldErrors.title}</span>
)}
<textarea name="content" />
<button type="submit">Criar</button>
</Form>
);
}
fetcher
Envia e obtém dados sem navegação.
import { useFetcher } from '@remix-run/react';
export default function LikeButton({ postId }: { postId: string }) {
const fetcher = useFetcher();
const isLiking = fetcher.state === 'submitting';
return (
<fetcher.Form method="post" action="/api/like">
<input type="hidden" name="postId" value={postId} />
<button type="submit" disabled={isLiking}>
{isLiking ? 'Curtindo...' : 'Curtir'}
</button>
</fetcher.Form>
);
}
defer e Suspense
import { defer } from '@remix-run/node';
import { Await, useLoaderData } from '@remix-run/react';
import { Suspense } from 'react';
export async function loader() {
// Dados necessários imediatamente
const user = await getUser();
// Dados para carregamento adiado
const recommendations = getRecommendations(); // sem await
return defer({
user,
recommendations
});
}
export default function Dashboard() {
const { user, recommendations } = useLoaderData<typeof loader>();
return (
<div>
<h1>Bem-vindo, {user.name}</h1>
<Suspense fallback={<div>Carregando recomendações...</div>}>
<Await resolve={recommendations}>
{(data) => <RecommendationsList items={data} />}
</Await>
</Suspense>
</div>
);
}
Tratamento de Erros
// routes/posts.$postId.tsx
import { isRouteErrorResponse, useRouteError } from '@remix-run/react';
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} {error.statusText}</h1>
<p>{error.data}</p>
</div>
);
}
return (
<div>
<h1>Erro</h1>
<p>{error instanceof Error ? error.message : 'Erro desconhecido'}</p>
</div>
);
}
Metadados
import { type MetaFunction } from '@remix-run/node';
export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [
{ title: data?.post.title ?? 'Não Encontrado' },
{ name: 'description', content: data?.post.excerpt },
{ property: 'og:title', content: data?.post.title }
];
};
Deploy
# Node.js
npm run build
npm start
# Cloudflare Pages
npm run build
npx wrangler pages deploy ./build/client
# Vercel
npm run build
# Configurar em vercel.json
Resumo
Remix v2 é um framework full-stack robusto que aproveita as tecnologias fundamentais da web. Com a melhoria da experiência de desenvolvimento através da integração com Vite, carregamento de dados type-safe e progressive enhancement, você pode construir aplicações web modernas de forma eficiente.
← Voltar para a lista