Safe Action Client

next-safe-action/skills/skills/safe-action-client

作者 next-safe-actiona2605bd2e84321245cba8f5718c144a6e4a5fa47无许可证收录于 2026年10月9日更新于 2026年10月9日

Use when creating or configuring a next-safe-action client, defining actions with input/output validation, handling server errors, or setting up createSafeActionClient with Standard Schema (Zod, Yup, Valibot)

AI 生成的概览

指导创建和配置 next-safe-action 客户端与操作,包含校验和错误处理。

功能
该技能提供在 Next.js 项目中构建 next-safe-action 客户端和服务端操作的参考说明。内容涵盖可链式调用的客户端 API、服务端与客户端钩子的入口、使用 Zod、Yup、Valibot 等 Standard Schema 库进行输入输出校验,以及服务端错误处理。它还列出反模式和服务端代码函数接收的参数,并指向客户端配置、校验和错误处理三份配套文档。
适用场景
适用于创建或配置 next-safe-action 客户端、定义带输入或输出校验的操作、处理服务端错误,或使用 Standard Schema 设置 createSafeActionClient 的场景。面向开发 Next.js 服务端操作的开发者。
运行要求
需要一个使用 next-safe-action 包的 Next.js 项目,以及 Zod、Yup 或 Valibot 等 Standard Schema 校验库。该技能不附带脚本,仅为说明文档。

next-safe-action Client & Action Definition

Quick Start

ts
// src/lib/safe-action.tsimport { createSafeActionClient } from "next-safe-action";
export const actionClient = createSafeActionClient();
ts
// src/app/actions.ts"use server";
import { z } from "zod";import { actionClient } from "@/lib/safe-action";
export const greetUser = actionClient  .inputSchema(z.object({ name: z.string().min(1) }))  .action(async ({ parsedInput: { name } }) => {    return { greeting: `Hello, ${name}!` };  });

Chainable API Order

createSafeActionClient(opts?)  .use(middleware)              // repeatable, adds pre-validation middleware  .metadata(data)              // required if defineMetadataSchema is set  .inputSchema(schema, utils?) // Standard Schema or async factory function  .bindArgsSchemas([...])      // schemas for .bind() arguments (order with inputSchema is flexible)  .useValidated(middleware)    // repeatable, adds post-validation middleware (requires inputSchema or bindArgsSchemas before it)  .outputSchema(schema)        // validates action return value  .action(serverCodeFn, utils?)      // creates SafeActionFn  .stateAction(serverCodeFn, utils?) // creates SafeStateActionFn (for useStateAction, useOptimisticStateAction, or React's useActionState)

Each method returns a new client instance — the chain is immutable.

Entry Points

Entry pointEnvironmentExports
next-safe-actionServercreateSafeActionClient, createMiddleware, createValidatedMiddleware, returnValidationErrors, returnServerError, flattenValidationErrors, formatValidationErrors, DEFAULT_SERVER_ERROR_MESSAGE, error classes, all core types
next-safe-action/hooksClientuseAction, useOptimisticAction, useStateAction, useOptimisticStateAction, hook types
next-safe-action/stateful-hooksClientuseStateAction (re-export from hooks for backward compatibility)

Supporting Docs

  • Client setup & configuration
  • Input & output validation with Standard Schema
  • Server error handling

Anti-Patterns

ts
// BAD: Missing "use server" directive — action won't workimport { actionClient } from "@/lib/safe-action";export const myAction = actionClient.action(async () => {});
// GOOD: Always include "use server" in action files"use server";import { actionClient } from "@/lib/safe-action";export const myAction = actionClient.action(async () => {});
ts
// BAD: Calling .action() without .metadata() when metadataSchema is definedconst client = createSafeActionClient({  defineMetadataSchema: () => z.object({ actionName: z.string() }),});client.action(async () => {}); // TypeScript error!
// GOOD: Always provide metadata before .action() when schema is definedclient  .metadata({ actionName: "myAction" })  .action(async () => {});
ts
// BAD: Returning an error instead of throwingexport const myAction = actionClient  .inputSchema(z.object({ email: z.string().email() }))  .action(async ({ parsedInput }) => {    const exists = await db.user.findByEmail(parsedInput.email);    if (exists) {      return { error: "Email taken" }; // Not type-safe, not standardized    }  });
// GOOD: Use returnValidationErrors for field-level errorsimport { returnValidationErrors } from "next-safe-action";
export const myAction = actionClient  .inputSchema(z.object({ email: z.string().email() }))  .action(async ({ parsedInput }) => {    const exists = await db.user.findByEmail(parsedInput.email);    if (exists) {      returnValidationErrors(z.object({ email: z.string().email() }), {        email: { _errors: ["Email is already in use"] },      });    }    return { success: true };  });

For expected non-validation business errors ("out of stock", "not found"), use returnServerError(payload) instead — it sets result.serverError to the typed payload, bypassing handleServerError. See Server error handling.

Server Code Function Parameters

The function passed to .action() receives a single object:

ts
.action(async ({  parsedInput,           // validated input (typed from inputSchema)  clientInput,           // raw client input (unknown)  bindArgsParsedInputs,  // validated bind args tuple  bindArgsClientInputs,  // raw bind args  ctx,                   // context from middleware chain  metadata,              // metadata set via .metadata()}) => {  // return data});

For .stateAction(), a second argument is added:

ts
.stateAction(async ({ parsedInput, ctx }, { prevResult }) => {  // prevResult is the previous SafeActionResult (structuredClone'd)  return { count: (prevResult.data?.count ?? 0) + 1 };});

来源与署名

来源:next-safe-action/skills位于skills/safe-action-client提交a2605bd

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架