Aller au contenu principal
Version : 11.x

Actions Serveur

Traduction Bêta Non Officielle

Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

Les Actions Serveur vous permettent de définir des fonctions sur le serveur et de les appeler directement depuis des composants clients, la couche réseau étant abstraite par le framework.

En définissant vos actions serveur via des procédures tRPC, vous bénéficiez de toutes ses fonctionnalités intégrées : validation des entrées, authentification et autorisation via des middlewares, validation des sorties, transformateurs de données, et plus encore.

info

L'intégration des Actions Serveur utilise le préfixe experimental_ et reste en développement actif. L'API peut évoluer dans les futures versions.

Configuration des procédures d'Action Serveur

1. Définir une procédure de base avec experimental_caller

Utilisez experimental_caller sur un constructeur de procédure avec experimental_nextAppDirCaller pour créer des procédures invocables comme fonctions standards (actions serveur). L'option pathExtractor permet d'identifier les procédures par métadonnées, utile pour la journalisation et l'observabilité car les actions serveur n'ont pas de chemin de routeur comme user.byId.

server/trpc.ts
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';
 
interface Meta {
span: string;
}
 
export const t = initTRPC.meta<Meta>().create();
 
export const serverActionProcedure = t.procedure.experimental_caller(
experimental_nextAppDirCaller({
pathExtractor: ({ meta }) => (meta as Meta)?.span ?? '',
}),
);
server/trpc.ts
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';
 
interface Meta {
span: string;
}
 
export const t = initTRPC.meta<Meta>().create();
 
export const serverActionProcedure = t.procedure.experimental_caller(
experimental_nextAppDirCaller({
pathExtractor: ({ meta }) => (meta as Meta)?.span ?? '',
}),
);

2. Ajouter le contexte via middleware

Les actions serveur ne transitant pas par un adaptateur HTTP, il n'y a pas de createContext pour injecter le contexte. Utilisez plutôt un middleware pour fournir des éléments comme les données de session :

server/trpc.ts
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';
import { currentUser } from '../auth';
 
interface Meta {
span: string;
}
 
export const t = initTRPC.meta<Meta>().create();
 
export const serverActionProcedure = t.procedure
.experimental_caller(
experimental_nextAppDirCaller({
pathExtractor: ({ meta }) => (meta as Meta)?.span ?? '',
}),
)
.use(async (opts) => {
const user = await currentUser();
return opts.next({ ctx: { user } });
});
server/trpc.ts
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';
import { currentUser } from '../auth';
 
interface Meta {
span: string;
}
 
export const t = initTRPC.meta<Meta>().create();
 
export const serverActionProcedure = t.procedure
.experimental_caller(
experimental_nextAppDirCaller({
pathExtractor: ({ meta }) => (meta as Meta)?.span ?? '',
}),
)
.use(async (opts) => {
const user = await currentUser();
return opts.next({ ctx: { user } });
});

3. Créer une procédure d'action protégée

Ajoutez un middleware d'autorisation pour créer une base réutilisable pour les actions nécessitant une authentification :

server/trpc.ts
ts
export const protectedAction = serverActionProcedure.use((opts) => {
if (!opts.ctx.user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
 
return opts.next({
ctx: {
...opts.ctx,
user: opts.ctx.user, // ensures type is non-nullable
},
});
});
server/trpc.ts
ts
export const protectedAction = serverActionProcedure.use((opts) => {
if (!opts.ctx.user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
 
return opts.next({
ctx: {
...opts.ctx,
user: opts.ctx.user, // ensures type is non-nullable
},
});
});

Définition d'une action serveur

Créez un fichier avec la directive "use server" et définissez votre action via le constructeur de procédure :

app/_actions.ts
ts
'use server';
 
import { z } from 'zod';
import { protectedAction } from '../server/trpc';
 
export const createPost = protectedAction
.input(
z.object({
title: z.string(),
}),
)
.mutation(async (opts) => {
// opts.ctx.user is typed as non-nullable
// opts.input is typed as { title: string }
// Create the post...
});
app/_actions.ts
ts
'use server';
 
import { z } from 'zod';
import { protectedAction } from '../server/trpc';
 
export const createPost = protectedAction
.input(
z.object({
title: z.string(),
}),
)
.mutation(async (opts) => {
// opts.ctx.user is typed as non-nullable
// opts.input is typed as { title: string }
// Create the post...
});

Grâce à experimental_caller, la procédure devient une fonction asynchrone standard utilisable comme action serveur.

Appel depuis des composants clients

Importez l'action serveur et utilisez-la dans un composant client. Les actions serveur fonctionnent avec l'attribut action pour l'amélioration progressive et les appels programmatiques via onSubmit :

app/post-form.tsx
tsx
'use client';
 
import { createPost } from '../_actions';
 
export function PostForm() {
return (
<form
onSubmit={async (e) => {
e.preventDefault();
const title = new FormData(e.currentTarget).get('title') as string;
await createPost({ title });
}}
>
<input type="text" name="title" />
<button type="submit">Create Post</button>
</form>
);
}
app/post-form.tsx
tsx
'use client';
 
import { createPost } from '../_actions';
 
export function PostForm() {
return (
<form
onSubmit={async (e) => {
e.preventDefault();
const title = new FormData(e.currentTarget).get('title') as string;
await createPost({ title });
}}
>
<input type="text" name="title" />
<button type="submit">Create Post</button>
</form>
);
}

Ajout d'observabilité avec des métadonnées

Utilisez la méthode .meta() pour étiqueter les actions en vue de journalisation ou de traçage. La propriété span des métadonnées est transmise à pathExtractor, permettant son utilisation par des outils d'observabilité :

app/_actions.ts
ts
'use server';
 
import { z } from 'zod';
import { protectedAction } from '../server/trpc';
 
export const createPost = protectedAction
.meta({ span: 'create-post' })
.input(
z.object({
title: z.string(),
}),
)
.mutation(async (opts) => {
// ...
});
app/_actions.ts
ts
'use server';
 
import { z } from 'zod';
import { protectedAction } from '../server/trpc';
 
export const createPost = protectedAction
.meta({ span: 'create-post' })
.input(
z.object({
title: z.string(),
}),
)
.mutation(async (opts) => {
// ...
});

Quand utiliser les Actions Serveur plutôt que des mutations

Les Actions Serveur ne remplacent pas toutes les mutations tRPC. Pesez les compromis :

  • Privilégiez les Actions Serveur pour l'amélioration progressive (formulaires fonctionnant sans JavaScript), ou quand l'action ne nécessite pas de mettre à jour le cache React Query côté client.

  • Utilisez useMutation lorsque vous devez mettre à jour le cache côté client, afficher des mises à jour optimistes, ou gérer des états de chargement/erreur complexes dans l'UI.

Vous pouvez adopter progressivement les actions serveur parallèlement à votre API tRPC existante - une réécriture complète n'est pas nécessaire.