¿Qué es Remix?
Remix es un framework de React construido sobre estándares web. Mientras que muchos frameworks abstraen la plataforma, Remix se apoya en ella: las solicitudes y respuestas HTTP, los formularios HTML, los enlaces, los headers y las cookies son la base, y el framework los potencia en lugar de reemplazarlos.
El resultado es una aplicación que funciona antes de que JavaScript se cargue. Los formularios se envían, los enlaces navegan y los datos aparecen porque el servidor se encarga del trabajo. Cuando JavaScript finalmente llega, esas mismas interacciones se vuelven instantáneas y parciales. Esta propiedad se llama progressive enhancement (mejora progresiva) y es la idea definitoria de Remix.
En 2024, el equipo de Remix integró el framework en React Router v7, donde ahora reside como “framework mode”. Los conceptos y las APIs se mantienen iguales, por lo que todo lo expuesto aquí se aplica a ambos.
Rutas anidadas
Las rutas son archivos dentro de app/routes, y los puntos en los nombres de los archivos se mapean a segmentos de la URL.
app/routes/
├── _index.tsx # /
├── posts._index.tsx # /posts
├── posts.$slug.tsx # /posts/:slug
├── posts.$slug.edit.tsx # /posts/:slug/edit
└── api.health.tsx # /api/health
Las rutas se anidan para coincidir con la UI. Una ruta padre renderiza el layout compartido y un <Outlet />, y las rutas hijas se renderizan dentro de este. Debido a que Remix conoce todo el árbol de rutas, puede cargar los datos de una página y de todas sus rutas anidadas en paralelo en lugar de hacerlo en cascada (waterfall).
// app/routes/posts.tsx
import { Outlet } from "@remix-run/react";
export default function PostsLayout() {
return (
<div className="posts">
<h1>Posts</h1>
<Outlet />
</div>
);
}
Loaders
Un loader es una función del servidor que proporciona datos a una ruta antes de que se renderice. Se ejecuta en el servidor, por lo que puede comunicarse directamente con una base de datos.
// app/routes/posts.$slug.tsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
export async function loader({ params }: LoaderFunctionArgs) {
const post = await getPost(params.slug);
if (!post) throw json({ message: "Post not found" }, { status: 404 });
return json({ post });
}
export default function Post() {
const { post } = useLoaderData<typeof loader>();
return <article>{post.title}</article>;
}
No hay que gestionar estados de carga ni realizar peticiones en el cliente. Remix llama al loader en el servidor para el renderizado inicial y nuevamente durante la navegación, y el componente recibe los datos de forma síncrona.
Acciones y formularios
Las mutaciones se gestionan mediante actions, que son funciones del servidor vinculadas a una ruta y que se activan al enviar formularios.
// app/routes/posts.new.tsx
import { redirect } from "@remix-run/node";
import { Form } from "@remix-run/react";
export async function action({ request }: ActionFunctionArgs) {
const data = await request.formData();
const title = String(data.get("title") ?? "").trim();
if (!title) return json({ error: "Title is required" }, { status: 400 });
await createPost(title);
return redirect("/posts");
}
export default function NewPost() {
return (
<Form method="post">
<input name="title" required />
<button type="submit">Create</button>
</Form>
);
}
Al tratarse de un formulario real, funciona sin JavaScript. Remix intercepta el envío cuando el cliente está listo y revalida automáticamente los loaders afectados, de modo que la UI refleja los nuevos datos sin necesidad de actualizar la caché manualmente.
Error boundaries
Cada ruta puede exportar un ErrorBoundary que captura los errores de su loader, action o componente.
// app/routes/posts.$slug.tsx
import { isRouteErrorResponse, useRouteError } from "@remix-run/react";
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error) && error.status === 404) {
return <h1>Post not found</h1>;
}
return <h1>Something went wrong</h1>;
}
Los boundaries están anidados, por lo que un error en una ruta hija no deja la página entera en blanco. El layout circundante sigue siendo funcional, lo cual ofrece una experiencia mucho mejor que una pantalla blanca.
Rutas de recurso y estándares web
Una ruta sin exportación predeterminada es una ruta de recurso: devuelve datos en lugar de UI, lo cual es ideal para APIs y webhooks.
// app/routes/api.health.tsx
export async function loader() {
return json({ status: "ok", time: Date.now() });
}
Debido a que Remix utiliza los estándares Request y Response de la plataforma, puedes leer headers, establecer cookies, transmitir respuestas y devolver cualquier código de estado sin tener que aprender una API específica del framework. Ese conocimiento se transfiere directamente a otros runtimes.
Por qué el modelo se siente diferente
La mayoría de las aplicaciones de React gestionan los datos en effects y los mantienen en el estado del cliente, para luego sincronizar ese estado con el servidor a través de rutas de API. Remix simplifica esas capas:
- La ruta es dueña de sus datos a través de un loader.
- La ruta es dueña de sus mutaciones a través de un action.
- El servidor posee la fuente de verdad y el cliente se mantiene ligero.
Menos piezas móviles significan menos bugs, menos estado que sincronizar y menos código que escribir. La contrapartida es una postura más definida sobre cómo fluyen los datos, que es precisamente la razón por la cual los equipos que lo adoptan suelen preferirlo.
Mejores prácticas
- Obtén los datos en loaders, no en effects.
- Realiza mutaciones a través de actions y forms, y deja que Remix se encargue de la revalidación.
- Utiliza
FormyLinkde Remix para que la mejora progresiva funcione automáticamente. - Exporta un
ErrorBoundaryen las rutas que puedan fallar. - Mantén las rutas anidadas alineadas con la jerarquía de la UI.
- Utiliza resource routes para APIs y webhooks.
- Devuelve los códigos de estado y headers adecuados desde los loaders y actions.
Errores comunes
- Hacer fetch en
useEffecten lugar de un loader y perder el renderizado del servidor. - Usar
fetchpara mutaciones en lugar de una action y revalidar manualmente. - Olvidar el
<Outlet />en una ruta de layout padre. - Silenciar errores en lugar de dejar que los boundaries los gestionen.
- Hacer over-fetching en un loader padre cuando una ruta hija debería ser la dueña de los datos.
- Tratar Remix como una SPA de lado del cliente y luchar contra su modelo de servidor.
Próximos pasos
Remix es la expresión más sólida de “use the platform” en el mundo de React. Compáralo con Next.js para conocer el otro enfoque principal de React, y con SvelteKit para ver una filosofía similar en otro ecosistema. Refuerza tus bases de React, TypeScript y Node.js, y luego construye una aplicación pequeña que incluya un loader, un action y un error boundary.