Qu’est-ce que Remix ?
Remix est un framework React basé sur les standards du web. Alors que de nombreux frameworks s’éloignent de la plateforme via des abstractions, Remix s’appuie dessus : les requêtes et réponses HTTP, les formulaires HTML, les liens, les headers et les cookies en sont le fondement, et le framework les améliore plutôt que de les remplacer.
Le résultat est une application qui fonctionne avant même que le JavaScript ne soit chargé. Les formulaires sont soumis, la navigation via les liens opère et les données s’affichent, car le serveur effectue le travail. Une fois que le JavaScript est chargé, ces mêmes interactions deviennent instantanées et partielles. Cette propriété s’appelle l’amélioration progressive (progressive enhancement), et c’est l’idée maîtresse de Remix.
En 2024, l’équipe de Remix a intégré le framework dans React Router v7, où il perdure sous la forme d’un “framework mode”. Les concepts et les API restent inchangés, tout ce qui est présenté ici s’applique donc aux deux.
Routes imbriquées
Les routes sont des fichiers situés sous app/routes, et les points dans les noms de fichiers correspondent aux segments d’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
Les routes s’imbriquent pour correspondre à l’interface utilisateur. Une route parente rend un layout partagé et un <Outlet />, et les routes enfants sont rendues à l’intérieur de celui-ci. Comme Remix connaît l’intégralité de l’arborescence des routes, il peut charger les données d’une page et de toutes ses routes imbriquées en parallèle plutôt que via un effet de cascade (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 est une fonction serveur qui fournit des données à une route avant son rendu. Il s’exécute côté serveur, ce qui lui permet de communiquer directement avec une base de données.
// 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>;
}
Il n’y a aucun état de chargement à gérer ni de requête côté client. Remix appelle le loader sur le serveur pour le rendu initial, puis à nouveau lors de la navigation, et le composant reçoit les données de manière synchrone.
Actions et formulaires
Les mutations sont gérées par des actions, qui sont des fonctions serveur liées à une route et déclenchées par la soumission de formulaires.
// 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>
);
}
Comme il s’agit d’un véritable formulaire, celui-ci fonctionne sans JavaScript. Remix intercepte la soumission lorsque le client est prêt et revalide automatiquement les loaders concernés, afin que l’interface utilisateur reflète les nouvelles données sans mise à jour manuelle du cache.
Error boundaries
Chaque route peut exporter un ErrorBoundary qui capture les erreurs provenant de son loader, de son action ou de son composant.
// 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>;
}
Les boundaries sont imbriquées, donc une erreur dans une route enfant n’efface pas l’intégralité de la page. Le layout parent reste fonctionnel, ce qui offre une expérience bien meilleure qu’un écran blanc.
Routes de ressources et standards du web
Une route sans export par défaut est une resource route : elle renvoie des données plutôt qu’une interface utilisateur, ce qui est idéal pour les API et les webhooks.
// app/routes/api.health.tsx
export async function loader() {
return json({ status: "ok", time: Date.now() });
}
Comme Remix utilise les Request et Response de la plateforme, vous pouvez lire les headers, définir des cookies, streamer des réponses et renvoyer n’importe quel code de statut sans avoir à apprendre une API spécifique au framework. Ces connaissances sont directement transférables à d’autres runtimes.
Pourquoi ce modèle semble différent
La plupart des applications React gèrent les données dans des effets et les conservent dans un état client, puis synchronisent cet état avec le serveur via des routes API. Remix fusionne ces couches :
- La route possède ses données via un loader.
- La route possède ses mutations via une action.
- Le serveur détient la source de vérité, et le client reste léger.
Moins de pièces mobiles signifie moins de bugs, moins d’états à synchroniser et moins de code à écrire. Le compromis réside dans une vision plus tranchée sur la circulation des données, ce qui est précisément la raison pour laquelle les équipes qui l’adoptent ont tendance à l’apprécier.
Bonnes pratiques
- Récupérez les données dans les loaders, et non dans les effects.
- Effectuez les mutations via des actions et des formulaires, et laissez Remix gérer la revalidation.
- Utilisez
FormetLinkde Remix pour que l’amélioration progressive fonctionne automatiquement. - Exportez un
ErrorBoundarysur les routes susceptibles d’échouer. - Gardez vos routes imbriquées alignées avec la hiérarchie de l’interface utilisateur.
- Utilisez des resource routes pour les API et les webhooks.
- Renvoyez les codes de statut et les headers appropriés depuis les loaders et les actions.
Erreurs courantes
- Effectuer des appels fetch dans
useEffectau lieu d’un loader, entraînant la perte du rendu serveur. - Utiliser
fetchpour les mutations au lieu d’une action et revalider les données manuellement. - Oublier le
<Outlet />dans une route de layout parente. - Intercepter les erreurs au lieu de laisser les boundaries les gérer.
- Faire du sur-fetching dans un loader parent alors qu’une route enfant devrait posséder les données.
- Traiter Remix comme une SPA côté client et lutter contre son modèle serveur.
Et après ?
Remix est l’expression la plus aboutie du concept “use the platform” dans l’univers React. Comparez-le avec Next.js pour découvrir l’autre approche majeure de React, et avec SvelteKit pour une philosophie similaire dans un autre écosystème. Renforcez vos bases en React, TypeScript et Node.js, puis créez une petite application comprenant un loader, une action et une error boundary.