Saltar al contenido principal
Versión: 11.x

Acciones de Servidor

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Las Acciones de Servidor te permiten definir funciones en el servidor y llamarlas directamente desde componentes cliente, con la capa de red abstraída por el framework.

Al definir tus acciones de servidor mediante procedimientos de tRPC, obtienes todas las funcionalidades incorporadas de tRPC: validación de entradas, autenticación y autorización mediante middlewares, validación de salidas, transformadores de datos y más.

información

La integración de Acciones de Servidor utiliza el prefijo experimental_ y sigue en desarrollo activo. La API podría cambiar en futuras versiones.

Configuración de procedimientos para Acciones de Servidor

1. Define un procedimiento base con experimental_caller

Usa experimental_caller en un constructor de procedimientos junto con experimental_nextAppDirCaller para crear procedimientos que puedan invocarse como funciones simples (acciones de servidor). La opción pathExtractor te permite identificar procedimientos mediante metadatos, lo cual es útil para registro y observabilidad ya que las acciones de servidor no tienen rutas de router como 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. Añade contexto mediante middleware

Dado que las acciones de servidor no pasan por un adaptador HTTP, no hay createContext para inyectar contexto. En su lugar, usa un middleware para proporcionar contexto como datos de sesión:

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. Crea un procedimiento de acción protegida

Añade un middleware de autorización para crear una base reutilizable para acciones que requieran autenticación:

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
},
});
});

Definición de una acción de servidor

Crea un archivo con la directiva "use server" y define tu acción usando el constructor de procedimientos:

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...
});

Gracias a experimental_caller, el procedimiento ahora es una función asíncrona simple que puede usarse como acción de servidor.

Llamada desde componentes cliente

Importa la acción de servidor y úsala en un componente cliente. Las acciones de servidor funcionan tanto con el atributo action para mejora progresiva como con llamadas programáticas mediante 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>
);
}

Añadiendo observabilidad con metadatos

Usa el método .meta() para etiquetar acciones con fines de registro o trazabilidad. La propiedad span de los metadatos se pasa a pathExtractor, por lo que puede ser utilizada por herramientas de observabilidad:

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) => {
// ...
});

Cuándo usar Acciones de Servidor vs mutaciones

Las Acciones de Servidor no reemplazan todas las mutaciones de tRPC. Considera las compensaciones:

  • Usa Acciones de Servidor cuando quieras mejora progresiva (formularios que funcionan sin JavaScript) o cuando la acción no necesite actualizar la caché de React Query en el cliente.

  • Usa useMutation cuando necesites actualizar la caché del cliente, mostrar actualizaciones optimistas o gestionar estados complejos de carga/error en la UI.

Puedes adoptar acciones de servidor gradualmente junto a tu API existente de tRPC - no es necesario reescribir toda tu API.