safe-action-better-auth
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesenext-safe-action Better Auth Adapter
next-safe-action Better Auth 适配器
Install
安装
bash
npm install @next-safe-action/adapter-better-auth better-authbash
npm install @next-safe-action/adapter-better-auth better-authImport
导入
ts
import { betterAuth } from "@next-safe-action/adapter-better-auth";ts
import { betterAuth } from "@next-safe-action/adapter-better-auth";Quick Start
快速开始
1. Set up Better Auth
1. 设置Better Auth
Create your Better Auth server instance. Add the plugin if your actions need to set cookies (e.g. , ):
nextCookies()signInEmailsignUpEmailts
// src/lib/auth.ts
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
export const auth = betterAuth({
// ...your config (database, plugins, etc.)
plugins: [
// ...other plugins
nextCookies(), // must be the last plugin in the array
],
});创建你的Better Auth服务器实例。如果你的操作需要设置Cookie(例如、),请添加插件:
signInEmailsignUpEmailnextCookies()ts
// src/lib/auth.ts
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
export const auth = betterAuth({
// ...你的配置(数据库、插件等)
plugins: [
// ...其他插件
nextCookies(), // 必须是数组中的最后一个插件
],
});2. Enable auth interrupts in Next.js
2. 在Next.js中启用auth interrupts
The default behavior uses from , which requires this flag:
unauthorized()next/navigationts
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
authInterrupts: true,
},
};
export default nextConfig;默认行为使用中的,这需要开启以下标识:
next/navigationunauthorized()ts
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
authInterrupts: true,
},
};
export default nextConfig;3. Create an authenticated action client
3. 创建已认证的操作客户端
ts
// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
import { betterAuth } from "@next-safe-action/adapter-better-auth";
import { auth } from "./auth";
// Public action client (no auth required)
export const actionClient = createSafeActionClient();
// Authenticated action client
export const authClient = actionClient.use(betterAuth(auth));ts
// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
import { betterAuth } from "@next-safe-action/adapter-better-auth";
import { auth } from "./auth";
// 公共操作客户端(无需认证)
export const actionClient = createSafeActionClient();
// 已认证操作客户端
export const authClient = actionClient.use(betterAuth(auth));4. Use it in your actions
4. 在操作中使用
ts
// src/app/actions.ts
"use server";
import { z } from "zod";
import { authClient } from "@/lib/safe-action";
export const updateProfile = authClient
.inputSchema(z.object({ name: z.string().min(1) }))
.action(async ({ parsedInput, ctx }) => {
// ctx.auth.user and ctx.auth.session are fully typed,
// including fields from Better Auth plugins
const userId = ctx.auth.user.id;
await db.user.update({
where: { id: userId },
data: { name: parsedInput.name },
});
return { success: true };
});ts
// src/app/actions.ts
"use server";
import { z } from "zod";
import { authClient } from "@/lib/safe-action";
export const updateProfile = authClient
.inputSchema(z.object({ name: z.string().min(1) }))
.action(async ({ parsedInput, ctx }) => {
// ctx.auth.user 和 ctx.auth.session 是完全类型化的,
// 包括来自Better Auth插件的字段
const userId = ctx.auth.user.id;
await db.user.update({
where: { id: userId },
data: { name: parsedInput.name },
});
return { success: true };
});How It Works
工作原理
betterAuth().use()- Fetches the session by calling using the request headers from
auth.api.getSession({ headers: await headers() })next/headers - Blocks unauthenticated requests by calling from
unauthorized()when no session existsnext/navigation - Injects typed context by passing to
{ auth: { user, session } }, merging it into the action contextnext()
The context is namespaced under to avoid collisions with other middleware context properties.
authbetterAuth().use()- 获取会话:使用中的请求头调用`auth.api.getSession({ headers: await headers() })
next/headers - 拦截未认证请求:当不存在会话时,调用中的
next/navigationunauthorized() - 注入类型化上下文:将传递给
{ auth: { user, session } },合并到操作上下文中next()
上下文命名为命名空间,以避免与其他中间件上下文属性冲突。
authType Inference
类型推断
The middleware infers the exact and types from your Better Auth instance, including any fields added by plugins. For example, if you use the plugin, will include . No manual type annotations are needed.
usersessionorganizationctx.auth.sessionactiveOrganizationId中间件会从你的Better Auth实例中推断出精确的和类型,包括插件添加的任何字段。例如,如果你使用插件,将包含。无需手动添加类型注解。
usersessionorganizationctx.auth.sessionactiveOrganizationIdEntry Points
入口点
| Entry point | Exports | Environment |
|---|---|---|
| | Server |
| 入口点 | 导出内容 | 环境 |
|---|---|---|
| | 服务端 |
Exported Types
导出类型
| Type | Description |
|---|---|
| The context shape added by the middleware: |
| The |
| The options object type for |
| 类型 | 描述 |
|---|---|
| 中间件添加的上下文结构: |
| |
| |
vs. Manual Auth Middleware
对比手动认证中间件
If you are using Better Auth, prefer over writing manual auth middleware. The adapter handles session fetching, cookie integration, typing, and unauthorized rejection automatically.
betterAuth(auth)ts
// Manual — don't do this if you have @next-safe-action/adapter-better-auth installed
const authClient = actionClient.use(async ({ next }) => {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) {
throw new Error("Unauthorized");
}
return next({ ctx: { userId: session.user.id } });
});
// With adapter — do this instead
const authClient = actionClient.use(betterAuth(auth));
// ctx.auth.user and ctx.auth.session are fully typed automatically如果你正在使用Better Auth,建议优先使用而不是编写手动认证中间件。适配器会自动处理会话获取、Cookie集成、类型化和未授权拒绝。
betterAuth(auth)ts
// 手动实现——如果已安装@next-safe-action/adapter-better-auth,请勿这样做
const authClient = actionClient.use(async ({ next }) => {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) {
throw new Error("Unauthorized");
}
return next({ ctx: { userId: session.user.id } });
});
// 使用适配器——推荐这样做
const authClient = actionClient.use(betterAuth(auth));
// ctx.auth.user 和 ctx.auth.session 会自动完成全类型化Supporting Docs
相关文档
- Custom authorize patterns (role checks, redirects, org access)
- 自定义授权模式(角色检查、重定向、组织访问)
Anti-Patterns
反模式
ts
// BAD: Missing nextCookies() plugin — cookies won't be set in Server Actions
// Session will silently be null when actions try to set cookies
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [/* no nextCookies() */],
});
// GOOD: Add nextCookies() as the last plugin
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
export const auth = betterAuth({
plugins: [
// ...other plugins
nextCookies(), // must be last
],
});ts
// BAD: Missing authInterrupts flag — unauthorized() will throw a runtime error
// next.config.ts
const nextConfig: NextConfig = {};
// GOOD: Enable authInterrupts
const nextConfig: NextConfig = {
experimental: {
authInterrupts: true,
},
};ts
// BAD: Re-fetching session inside authorize — it's already pre-fetched as authData
actionClient.use(
betterAuth(auth, {
authorize: async ({ next }) => {
const session = await auth.api.getSession({ headers: await headers() }); // Redundant!
if (!session || session.user.role !== "admin") {
unauthorized();
}
return next({ ctx: { auth: session } });
},
})
);
// GOOD: Use the pre-fetched authData directly
actionClient.use(
betterAuth(auth, {
authorize: ({ authData, next }) => {
if (!authData || authData.user.role !== "admin") {
unauthorized();
}
return next({ ctx: { auth: authData } });
},
})
);ts
// BAD: Writing manual Better Auth middleware when the adapter is installed
import { auth } from "./auth";
const authClient = actionClient.use(async ({ next }) => {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) throw new Error("Unauthorized");
return next({ ctx: { user: session.user } });
});
// GOOD: Use the adapter — handles typing, cookies, and unauthorized() automatically
import { betterAuth } from "@next-safe-action/adapter-better-auth";
import { auth } from "./auth";
const authClient = actionClient.use(betterAuth(auth));ts
// 错误:缺少nextCookies()插件——Server Actions中无法设置Cookie
// 当操作尝试设置Cookie时,会话会静默变为null
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [/* 没有nextCookies() */],
});
// 正确:添加nextCookies()作为最后一个插件
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
export const auth = betterAuth({
plugins: [
// ...其他插件
nextCookies(), // 必须是最后一个
],
});ts
// 错误:缺少authInterrupts标识——unauthorized()会抛出运行时错误
// next.config.ts
const nextConfig: NextConfig = {};
// 正确:启用authInterrupts
const nextConfig: NextConfig = {
experimental: {
authInterrupts: true,
},
};ts
// 错误:在authorize中重新获取会话——authData已经预获取
actionClient.use(
betterAuth(auth, {
authorize: async ({ next }) => {
const session = await auth.api.getSession({ headers: await headers() }); // 冗余!
if (!session || session.user.role !== "admin") {
unauthorized();
}
return next({ ctx: { auth: session } });
},
})
);
// 正确:直接使用预获取的authData
actionClient.use(
betterAuth(auth, {
authorize: ({ authData, next }) => {
if (!authData || authData.user.role !== "admin") {
unauthorized();
}
return next({ ctx: { auth: authData } });
},
})
);ts
// 错误:已安装适配器时仍编写手动Better Auth中间件
import { auth } from "./auth";
const authClient = actionClient.use(async ({ next }) => {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) throw new Error("Unauthorized");
return next({ ctx: { user: session.user } });
});
// 正确:使用适配器——自动处理类型化、Cookie和unauthorized()
import { betterAuth } from "@next-safe-action/adapter-better-auth";
import { auth } from "./auth";
const authClient = actionClient.use(betterAuth(auth));