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 從公開儲存庫中收錄這些內容。

檢舉或申請下架