メインコンテンツへスキップ
バージョン: 11.x

サーバーアクション

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

Server Actions を使用すると、サーバー上で関数を定義し、フレームワークによってネットワーク層が抽象化された状態でクライアントコンポーネントから直接呼び出すことができます。

tRPC プロシージャを使用して Server Actions を定義することで、tRPC の組み込み機能をすべて利用できます。これには入力検証、ミドルウェアによる認証・認可、出力検証、データトランスフォーマーなどが含まれます。

情報

Server Actions 統合は experimental_ プレフィックスを使用しており、現在も活発に開発中です。API は将来のリリースで変更される可能性があります。

Server Action プロシージャの設定

1. experimental_caller で基本プロシージャを定義

プロシージャビルダーで experimental_callerexperimental_nextAppDirCaller を使用し、プレーン関数(Server Actions)として呼び出せるプロシージャを作成します。pathExtractor オプションでメタデータによるプロシージャ識別が可能になり、Server Actions には 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. ミドルウェアでコンテキストを追加

Server Actions は HTTP アダプターを経由しないため、createContext でコンテキストを注入できません。代わりにミドルウェアを使用して、セッションデータなどのコンテキストを提供します:

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. 保護されたアクションプロシージャを作成

認証が必要なアクション向けの再利用可能なベースを作成するため、認可ミドルウェアを追加します:

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

Server Action の定義

"use server" ディレクティブを含むファイルを作成し、プロシージャビルダーを使用してアクションを定義します:

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

experimental_caller により、プロシージャは Server Action として使用できるプレーンな非同期関数になります。

クライアントコンポーネントからの呼び出し

Server Action をインポートし、クライアントコンポーネントで使用します。Server Actions はプログレッシブエンハンスメントのための action 属性と、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>
);
}

メタデータによるオブザーバビリティの追加

ロギングやトレーシングのためにアクションにタグ付けするには .meta() メソッドを使用します。メタデータの span プロパティは pathExtractor に渡されるため、オブザーバビリティツールで利用できます:

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

Server Actions とミューテーションの使い分け

Server Actions はすべての tRPC ミューテーションの代替ではありません。以下のトレードオフを考慮してください:

  • Server Actions を使用するケース: プログレッシブエンハンスメント(JavaScript なしで動作するフォーム)が必要な場合、またはアクションがクライアントサイドの React Query キャッシュを更新する必要がない場合。

  • useMutation を使用するケース: クライアントサイドキャッシュの更新、オプティミスティックアップデートの表示、UI での複雑なローディング/エラー状態の管理が必要な場合。

既存の tRPC API と並行して Server Actions を段階的に導入できます。API 全体を書き直す必要はありません。