Safe Action Middleware

next-safe-action/skills/skills/safe-action-middleware

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

Use when implementing middleware for next-safe-action -- authentication, authorization, logging, rate limiting, error interception, context extension, or creating standalone reusable middleware with createMiddleware() or createValidatedMiddleware(). Covers both use() (pre-validation) and useValidated() (post-validation) middleware.

AI 生成的概览

指导实现 next-safe-action 中间件,涵盖认证、日志、限流、错误处理与上下文扩展。

功能
说明如何为 next-safe-action 库构建中间件链,涵盖 .use() 验证前中间件与 .useValidated() 验证后中间件。文档介绍函数签名、执行顺序、上下文累积与深度合并、链式调用规则、模式转换,以及错误回调中上下文的可用性。还列出反模式,例如忘记返回 next() 或吞掉框架错误,并指向认证模式、日志和独立中间件等支持文档。
适用场景
在为 next-safe-action 服务端操作添加认证、授权、日志、限流、错误拦截或上下文扩展时使用。也适用于在 .use() 与 .useValidated() 之间做选择,或用 createMiddleware()、createValidatedMiddleware() 创建可复用的独立中间件时。
运行要求
无需脚本或工具,智能体只需技能说明。内容面向 next-safe-action 库与 TypeScript/Next.js 项目,示例涉及会话辅助函数、数据库和 Zod 模式。

next-safe-action Middleware

Quick Start

ts
import { createSafeActionClient } from "next-safe-action";
const actionClient = createSafeActionClient();
// Add middleware with .use()const authClient = actionClient.use(async ({ next }) => {  const session = await getSession();  if (!session?.user) {    throw new Error("Unauthorized");  }  // Pass context to the next middleware/action via next({ ctx })  return next({    ctx: { userId: session.user.id },  });});

How Middleware Works

  • .use() adds middleware to the chain — you can call it multiple times
  • Each .use() returns a new client instance (immutable chain)
  • Middleware executes top-to-bottom (in the order added)
  • Results flow bottom-to-top (the deepest middleware/action resolves first)
  • Context is accumulated via next({ ctx }) — each level's ctx is deep-merged with the previous
ts
const client = createSafeActionClient()  .use(async ({ next }) => {    console.log("1: before");             // Runs 1st    const result = await next({ ctx: { a: 1 } });    console.log("1: after");              // Runs 4th    return result;  })  .use(async ({ next, ctx }) => {    console.log("2: before", ctx.a);      // Runs 2nd, ctx.a = 1    const result = await next({ ctx: { b: 2 } });    console.log("2: after");              // Runs 3rd    return result;  });
// In .action(): ctx = { a: 1, b: 2 }

use() Middleware Function Signature

ts
async ({  clientInput,           // Raw input from the client (unknown)  bindArgsClientInputs,  // Raw bind args array  ctx,                   // Accumulated context from previous middleware  metadata,              // Metadata set via .metadata()  next,                  // Call to proceed to next middleware/action}) => {  // Optionally extend context  return next({ ctx: { /* new context properties */ } });}

useValidated() — Post-Validation Middleware

.useValidated() registers middleware that runs after input validation, giving access to typed parsedInput. Default to use() — only use useValidated() when middleware logic depends on validated input.

ts
const action = authClient  .inputSchema(z.object({ postId: z.string().uuid(), title: z.string() }))  .useValidated(async ({ parsedInput, ctx, next }) => {    // parsedInput is typed: { postId: string; title: string }    const post = await db.post.findById(parsedInput.postId);    if (!post || post.authorId !== ctx.userId) {      throw new Error("Not authorized");    }    return next({ ctx: { post } });  })  .action(async ({ parsedInput, ctx }) => {    // ctx.post is available and typed    await db.post.update(ctx.post.id, { title: parsedInput.title });  });

Execution Order

1. use() middleware            — pre-validation, runs in order added2. Input validation            — schema parsing3. useValidated() middleware   — post-validation, runs in order added4. Server code (.action())     — receives final ctx and parsedInput

Both middleware stacks follow the onion model: code before next() runs top-to-bottom, code after next() unwinds bottom-to-top.

use() vs useValidated()

NeedMethod
Authentication, logging, rate limiting (no input needed).use()
Access to raw clientInput before validation.use()
Authorization based on validated input (e.g., check user owns resource).useValidated()
Logging or auditing validated/transformed input.useValidated()
Enriching context with data derived from parsed input.useValidated()

useValidated() Middleware Function Signature

ts
async ({  parsedInput,             // Validated, typed input (from inputSchema)  clientInput,             // Raw input from the client  bindArgsParsedInputs,    // Validated bind args tuple  bindArgsClientInputs,    // Raw bind args array  ctx,                     // Accumulated context from all previous middleware  metadata,                // Metadata set via .metadata()  next,                    // Call to proceed to next middleware/action}) => {  return next({ ctx: { /* new context properties */ } });}

Chaining Rules

  • Must call .inputSchema() or .bindArgsSchemas() before .useValidated()
  • Cannot call .inputSchema() or .bindArgsSchemas() after .useValidated()
  • Cannot call .use() after .useValidated()
  • Can chain multiple .useValidated() calls

Schema Transforms

useValidated() sees the transformed value in parsedInput, while clientInput retains the original:

ts
authClient  .inputSchema(z.string().transform((s) => s.toUpperCase()))  .useValidated(async ({ clientInput, parsedInput, next }) => {    console.log(clientInput);  // "hello" (original)    console.log(parsedInput);  // "HELLO" (transformed)    return next();  })

Context in Error Callbacks

  • Context set by use() middleware is always available in onError/onSettled callbacks.
  • Context set by useValidated() middleware is optional (may be undefined) — if validation fails, useValidated() never runs, so its context additions are missing.

Supporting Docs

  • Authentication & authorization patterns
  • Logging & monitoring middleware
  • Standalone reusable middleware with createMiddleware() and createValidatedMiddleware()

Anti-Patterns

ts
// BAD: Forgetting to return next() — action will hang.use(async ({ next }) => {  await doSomething();  next({ ctx: {} }); // Missing return!})
// GOOD: Always return the result of next().use(async ({ next }) => {  await doSomething();  return next({ ctx: {} });})
ts
// BAD: Catching all errors (swallows framework errors like redirect/notFound).use(async ({ next }) => {  try {    return await next({ ctx: {} });  } catch (error) {    return { serverError: "Something went wrong" }; // Swallows redirect!  }})
// GOOD: Re-throw framework errors.use(async ({ next }) => {  try {    return await next({ ctx: {} });  } catch (error) {    if (error instanceof Error && "digest" in error) {      throw error; // Let Next.js handle redirects, notFound, etc.    }    // Handle other errors    console.error(error);    return { serverError: "Something went wrong" };  }})
ts
// BAD: useValidated() without an input schema — won't compileconst client = actionClient.useValidated(async ({ parsedInput, next }) => {  return next();});
// GOOD: Always define inputSchema or bindArgsSchemas before useValidated()const client = actionClient  .inputSchema(z.object({ id: z.string() }))  .useValidated(async ({ parsedInput, next }) => {    console.log(parsedInput.id); // Typed!    return next();  });

来源与署名

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

许可证: 无许可证

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

举报或申请下架