Loaders & Actions
Une route charge ses données avec un loader et gère les mutations avec une action. Les deux sont
exportés depuis le fichier de route, aux côtés de la page :
loader: ({ request, params }) => unknown | Promise<unknown>;
action: ({ request, params }) => unknown | Promise<unknown>;
Cycle de vie
L'ordre d'exécution dépend de la méthode HTTP :
- Sur
POST/PUT/PATCH/DELETE, l'actions'exécute en premier. - Le
loaders'exécute pour toutes les méthodes — y compris après une action réussie. - Le composant de page se rend avec les résultats.
Les valeurs de retour arrivent dans la page sous forme de props :
| Fonction | S'exécute sur | Retour → prop |
|---|---|---|
loader |
toutes les méthodes | props.data |
action |
méthodes mutantes (POST/PUT/PATCH/DELETE) | props.actionData |
Accès à la requête et aux sessions
Le composant de page ne voit jamais request — seuls loader et action le voient. Lisez-y
les cookies et en-têtes :
import { readSession } from "@/lib/session";
export function loader({ request }) {
const session = readSession(request); // reads the session cookie off request.headers
return { user: session?.user ?? null };
}
readSession(request) lit le cookie de session depuis request.headers. Transmettez ce dont la
page a besoin via la valeur de retour ; n'essayez pas d'atteindre request depuis le composant.
Suivi des lectures d'en-têtes (vary)
Le runtime enregistre quels noms d'en-têtes un loader lit — le « vary set » — et s'en sert pour la mise en cache. Ne lisez que les en-têtes dont vous avez réellement besoin. Lire un en-tête large ou propre à chaque requête (cookies, auth) fait varier la réponse en fonction de celui-ci, ce qui réduit les hits de cache ; ne toucher à rien garde la route cacheable.
Court-circuiter le rendu
Retourner ou lancer une Response depuis l'une ou l'autre fonction court-circuite le rendu de
la page (façon Remix). Utilisez cela pour les redirections et les 404 :
import { redirect, notFound } from "@bext-stack/framework";
export function loader({ request, params }) {
const session = readSession(request);
if (!session) throw redirect("/login");
const post = getPost(params.slug);
if (!post) throw notFound();
return { post };
}
- Retournez une
Responsepour l'envoyer directement. - Lancez une
Responsepour interrompre un traitement plus profond et l'envoyer.
Frontière d'erreur
Les erreurs non rattrapées sont interceptées par l'error.tsx du segment :
/** @jsxImportSource @bext-stack/framework */
export default function Error({ error, reset, route }) {
return (
<div role="alert">
<p>Something went wrong: {String(error)}</p>
<button onClick="location.reload()">Try again</button>
</div>
);
}
reset retente le segment ; route décrit la route en échec.
Exemple complet : page + loader + action
/** @jsxImportSource @bext-stack/framework */
import { redirect } from "@bext-stack/framework";
import { readSession } from "@/lib/session";
import { createPost, listPosts } from "@/lib/posts";
// Runs first on POST, before the loader.
export async function action({ request }) {
const session = readSession(request);
if (!session) throw redirect("/login");
const form = await request.formData();
const title = String(form.get("title") ?? "");
if (!title) return { error: "Title is required" };
await createPost({ authorId: session.user.id, title });
return { ok: true };
}
// Runs on every method (and after a successful action).
export async function loader({ request }) {
const session = readSession(request);
return { posts: await listPosts(), signedIn: Boolean(session) };
}
export default function Page({ data, actionData }) {
return (
<main>
{actionData?.error && <p role="alert">{actionData.error}</p>}
<form method="post">
<input name="title" placeholder="Title" />
<button type="submit">Create</button>
</form>
<ul>{data.posts.map((p) => `<li>${p.title}</li>`).join("")}</ul>
</main>
);
}
Exemple de route API (route.ts)
Un route.ts gère le HTTP brut avec une fonction par méthode — sans rendu de page :
import { json } from "@bext-stack/framework";
import { listPosts, createPost } from "@/lib/posts";
export async function GET(request: Request) {
return json(await listPosts());
}
export async function POST(request: Request) {
const body = await request.json();
const post = await createPost(body);
return json(post, { status: 201 });
}
Et ensuite
| Page | Ce qu'elle couvre |
|---|---|
| Routage & Rendu | Fichiers de route, routes dynamiques, ISR/SSR, helpers de réponse |
| KV Store | Persister un petit état qu'un loader peut lire |
| Entités (Neon) | CRUD piloté par configuration et accès direct à Neon |
| Sessions & Tokens | Comment le cookie / token de session est résolu |