Next.js 新增的 Server Actions 功能,可以让我们用更简单、安全的方式实现无状态会话,不需要写传统的 REST API,也不用依赖外部存储服务(如 Redis)。本文将用最基础的知识点,结合代码示例,帮你快速掌握如何用 Server Actions 和 Cookie 实现无状态会话。
什么是无状态会话?
无状态会话指的是服务器不保存用户的登录状态数据,所有状态信息都存储在客户端(比如 Cookie)里。服务器每次请求时,只通过客户端传来的信息判断用户身份。这样服务器不用维护会话数据,性能更好,扩展性更强。
什么是 Next.js Server Actions?
Server Actions 是 Next.js 13.4 及以后版本引入的一个新功能,允许你直接在 React 组件里调用服务器端的异步函数,省去了写 API 接口的麻烦。它本质上是用 POST 请求调用服务器函数,但你写起来就像调用本地函数一样简单。
- Server Actions 用
"use server"指令标记。 - 支持在服务器组件和客户端组件中调用。
- 支持表单的无刷新提交和渐进增强。
- 参数和返回值必须可被 React 序列化。
- 可以操作 Cookie、数据库等服务器资源。
为什么用 Server Actions 实现无状态会话?
传统会话依赖服务器内存或外部存储,难以扩展且增加复杂度。使用 Server Actions + Cookie 的方式:
- 会话数据加密后存储在客户端 Cookie 中,安全且无状态。
- 不需要额外的数据库或缓存系统。
- 服务器负载更轻,部署更简单。
详细步骤与代码示例
1. 初始化项目
npx create-next-app@latest my-nextjs-app
cd my-nextjs-app
2. 创建会话管理库(session/index.ts)
"use server";
import { cookies } from 'next/headers';
import { encrypt, decrypt } from './encrypt'; // 加密模块,后面介绍
export type Session = {
username: string;
};
// 从 Cookie 读取并解密会话
export const getSession = async (): Promise<Session | null> => {
const cookieStore = cookies();
const session = cookieStore.get('session');
if (session?.value) {
try {
const decrypted = decrypt(session.value);
return JSON.parse(decrypted) as Session;
} catch {
return null; // 解密失败视为无效会话
}
}
return null;
};
// 加密并写入 Cookie
export const setSession = async (session: Session) => {
const cookieStore = cookies();
const encrypted = encrypt(JSON.stringify(session));
cookieStore.set('session', encrypted);
};
// 删除 Cookie
export const removeSession = async () => {
const cookieStore = cookies();
cookieStore.delete('session');
};
3. 实现加密模块(session/encrypt.ts)
import { createCipheriv, createDecipheriv } from 'crypto';
// 这里使用 AES-256-CBC 算法,密钥和 IV 需要自己生成并保密
const key = Buffer.from('17204a84b538359abe8ba74807efa12a068c20a7c7f224b35198acf832cea57b', 'hex');
const iv = Buffer.from('da1cdcd9fe4199c835bd5f1d56446aff', 'hex');
const algorithm = 'aes-256-cbc';
// 加密函数
export const encrypt = (text: string) => {
const cipher = createCipheriv(algorithm, key, iv);
const encrypted = cipher.update(text, 'utf8', 'base64');
return encrypted + cipher.final('base64');
};
// 解密函数
export const decrypt = (encrypted: string) => {
const decipher = createDecipheriv(algorithm, key, iv);
const decrypted = decipher.update(encrypted, 'base64', 'utf8');
return decrypted + decipher.final('utf8');
};
提示:你可以用
crypto.randomBytes(32)生成密钥,crypto.randomBytes(16)生成 iv,确保安全。
4. 实现登录和登出 Server Actions(user/index.ts)
"use server";
import { setSession, removeSession } from '@/session';
export const signIn = async (username: string) => {
await setSession({ username });
};
export const signOut = async () => {
await removeSession();
};
5. 创建客户端登录组件(components/sign-in.tsx)
"use client";
import { signIn } from '@/user';
import { useState } from 'react';
const SignIn = () => {
const [username, setUsername] = useState('');
return (
<div>
<input
type="text"
placeholder="请输入用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<button disabled={!username} onClick={() => signIn(username)}>
登录
</button>
</div>
);
};
export default SignIn;
6. 创建客户端登出组件(components/sign-out.tsx)
"use client";
import { signOut } from '@/user';
const SignOut = () => {
return <button onClick={() => signOut()}>登出</button>;
};
export default SignOut;
7. 创建首页显示登录状态(app/page.tsx)
import { getSession } from '@/session';
import SignIn from '@/components/sign-in';
import SignOut from '@/components/sign-out';
export default async function Home() {
const session = await getSession();
return (
<main style={{ padding: '20px', fontSize: '18px' }}>
{session ? (
<>
<p>欢迎,{session.username}!你已登录。</p>
<SignOut />
</>
) : (
<SignIn />
)}
</main>
);
}
运行效果
- 用户输入用户名点击登录,服务器端会话数据加密后写入 Cookie。
- 页面刷新或跳转时,服务端读取 Cookie 并解密,显示用户登录状态。
- 点击登出按钮,删除 Cookie,用户退出登录状态。
关键知识点总结
- Server Actions:用
"use server"标记的异步函数,自动生成服务器端接口,客户端调用就像调用本地函数。 - 无状态会话:会话数据存储在客户端 Cookie 中,服务器不保存状态,适合无服务器环境。
- Cookie 读写:使用
next/headers中的cookies()读取和设置 Cookie。 - 加密安全:使用 AES-256-CBC 对会话数据加密,防止被篡改和窃取。
- 客户端组件与服务端组件:登录登出按钮用
"use client"标记,支持交互;首页用服务端组件异步获取会话状态。
额外示例:用表单调用 Server Action
你也可以用 <form> 的 action 属性调用 Server Action,实现无刷新表单提交:
// serverActions.ts
"use server";
export async function submitForm(data: FormData) {
const username = data.get('username')?.toString() || '';
// 处理登录逻辑
await signIn(username);
}
// ClientForm.tsx
"use client";
import { submitForm } from '@/serverActions';
export default function ClientForm() {
return (
<form action={submitForm}>
<input name="username" type="text" placeholder="用户名" />
<button type="submit">登录</button>
</form>
);
}
这样,表单提交时会自动调用服务器端的 submitForm 函数,无需写额外的 API。
总结
通过 Next.js 的 Server Actions 和 Cookie 加密技术,我们可以轻松实现无状态会话,避免传统 REST API 的繁琐,提升开发效率和应用性能。以上示例代码清晰展示了从项目初始化到加密会话的完整流程,适合初学者快速上手。
想了解更多 Server Actions 的高级用法和实战技巧,可以参考 Next.js 官方文档和社区教程。祝你开发顺利!