Hoppa till huvudinnehållet
Version: 11.x

Server Actions

Inofficiell Beta-översättning

Denna sida har översatts av PageTurner AI (beta). Inte officiellt godkänd av projektet. Hittade du ett fel? Rapportera problem →

Server Actions låter dig definiera funktioner på servern och anropa dem direkt från klientkomponenter, med nätverkslagret abstraherat bort av ramverket.

Genom att definiera dina server actions med tRPC-procedurer får du alla tRPC:s inbyggda funktioner: validering av indata, autentisering och auktorisering via middleware, validering av utdata, datatransformeringar med mera.

information

Server Actions-integrationen använder prefixet experimental_ och är fortfarande under aktiv utveckling. API:t kan komma att ändras i framtida versioner.

Konfigurera Server Action-procedurer

1. Definiera en basprocedur med experimental_caller

Använd experimental_caller på en procedurbuilder tillsammans med experimental_nextAppDirCaller för att skapa procedurer som kan anropas som vanliga funktioner (server actions). Alternativet pathExtractor låter dig identifiera procedurer via metadata, vilket är användbart för loggning och observability eftersom server actions saknar router-sökvägar som 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. Lägg till kontext via middleware

Eftersom server actions inte passerar genom en HTTP-adapter finns det ingen createContext för att injicera kontext. Använd istället en middleware för att tillhandahålla kontext som sessionsdata:

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. Skapa en skyddad action-procedur

Lägg till en auktoriseringsmiddleware för att skapa en återanvändbar bas för actions som kräver autentisering:

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

Definiera en server action

Skapa en fil med direktivet "use server" och definiera din action med procedurbuildern:

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

På grund av experimental_caller är proceduren nu en vanlig asynkron funktion som kan användas som server action.

Anropa från klientkomponenter

Importera server action:en och använd den i en klientkomponent. Server actions fungerar både med action-attributet för progressiv förbättring och programmatiska anrop 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>
);
}

Lägg till observability med metadata

Använd metoden .meta() för att tagga actions för loggning eller spårning. span-egenskapen från metadatan skickas till pathExtractor, så den kan användas av observability-verktyg:

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

När Server Actions är att föredra framför mutations

Server Actions är inte en ersättning för alla tRPC-mutations. Tänk på avvägningarna:

  • Använd Server Actions när du vill ha progressiv förbättring (formulär som fungerar utan JavaScript), eller när action:en inte behöver uppdatera React Query-cachen på klientsidan.

  • Använd useMutation när du behöver uppdatera klient-sidans cache, visa optimistiska uppdateringar eller hantera komplexa laddnings-/felstatusar i användargränssnittet.

Du kan adoptera server actions stegvis bredvid ditt befintliga tRPC-API - det finns inget behov av att skriva om hela ditt API.