关于 nextjs-client-cookie-pattern
面向 Next.js App Router 开发者的客户端交互与服务器端 Cookie 设置模式指南。通过客户端组件调用服务器操作,在按钮或表单操作后设置 Cookie。
主要功能
- 01客户端组件处理点击与表单
- 02服务器操作设置 Cookie
- 03使用 next/headers 读写 Cookie
- 04提供主题、同意和语言偏好示例
- 05说明 Cookie 安全选项
适用场景
- 01点击按钮保存主题偏好
- 02提交 Cookie 同意横幅
- 03保存语言选择
- 04设置 Cookie 后执行重定向
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wsimmonds/ claude-nextjs-skills --skill nextjs-client-cookie-pattern安装后在 Claude Code 中输入/nextjs-client-cookie-pattern确认已加载。
详细介绍
Next.js:客户端组件 + Server Action Cookie 模式
模式概述
此模式用于满足常见的 Next.js 需求:客户端交互(点击按钮)需要设置服务端 Cookie。
为什么需要两个文件?
- 客户端组件(
'use client')可以使用 onClick 处理程序 - 只有服务端代码才能设置 Cookie(安全要求)
- 解决方案:客户端组件调用用于设置 Cookie 的 Server Action
此模式
**场景:**点击按钮时设置 Cookie
文件 1:客户端组件(app/CookieButton.tsx)
- 包含
'use client'指令 - 包含 onClick 处理程序
- 导入并调用 Server Action
文件 2:Server Action(app/actions.ts)
- 包含
'use server'指令 - 使用
next/headers中的cookies() - 设置 Cookie
完整实现
文件 1:客户端组件
// app/CookieButton.tsx
'use client';
import { setPreference } from './actions';
export default function CookieButton() {
const handleClick = async () => {
await setPreference('dark-mode', 'true');
};
return (
<button onClick={handleClick}>
Enable Dark Mode
</button>
);
}
文件 2:Server Action
// app/actions.ts
'use server';
import { cookies } from 'next/headers';
export async function setPreference(key: string, value: string) {
const cookieStore = await cookies();
cookieStore.set(key, value, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 365, // 1 year
});
}
文件结构
app/
├── CookieButton.tsx ← Client component
├── actions.ts ← Server actions
└── page.tsx ← Uses CookieButton
TypeScript:绝不使用 any 类型
此代码库启用了 @typescript-eslint/no-explicit-any。
// ❌ WRONG
async function setCookie(key: any, value: any) { ... }
// ✅ CORRECT
async function setCookie(key: string, value: string) { ... }
实际示例
示例 1:主题切换
// app/ThemeToggle.tsx
'use client';
import { useState } from 'react';
import { setTheme } from './actions';
export default function ThemeToggle() {
const [theme, setLocalTheme] = useState('light');
const toggle = async () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setLocalTheme(newTheme);
await setTheme(newTheme);
};
return (
<button onClick={toggle} className={theme}>
{theme === 'light' ? '🌙' : '☀️'} Toggle Theme
</button>
);
}
// app/actions.ts
'use server';
import { cookies } from 'next/headers';
export async function setTheme(theme: 'light' | 'dark') {
const cookieStore = await cookies();
cookieStore.set('theme', theme, {
httpOnly: false, // Allow client to read it
maxAge: 60 * 60 * 24 * 365,
});
}
示例 2:Cookie 同意横幅
// app/components/CookieBanner.tsx
'use client';
import { useState } from 'react';
import { acceptCookies } from '../actions';
export default function CookieBanner() {
const [visible, setVisible] = useState(true);
const handleAccept = async () => {
await acceptCookies();
setVisible(false);
};
if (!visible) return null;
return (
<div className="cookie-banner">
<p>We use cookies to improve your experience.</p>
<button onClick={handleAccept}>Accept</button>
</div>
);
}
// app/actions.ts
'use server';
import { cookies } from 'next/headers';
export async function acceptCookies() {
const cookieStore = await cookies();
cookieStore.set('cookies-accepted', 'true', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 365,
});
}
示例 3:语言偏好设置
// app/LanguageSelector.tsx
'use client';
import { setLanguage } from './actions';
export default function LanguageSelector() {
const languages = ['en', 'es', 'fr', 'de'];
return (
<select onChange={(e) => setLanguage(e.target.value)}>
{languages.map((lang) => (
<option key={lang} value={lang}>
{lang.toUpperCase()}
</option>
))}
</select>
);
}
// app/actions.ts
'use server';
import { cookies } from 'next/headers';
export async function setLanguage(lang: string) {
const cookieStore = await cookies();
cookieStore.set('language', lang, {
httpOnly: false,
maxAge: 60 * 60 * 24 * 365,
});
}
Cookie 选项
cookieStore.set('name', 'value', {
httpOnly: true, // Prevents JavaScript access (security)
secure: true, // Only send over HTTPS
sameSite: 'lax', // CSRF protection
maxAge: 3600, // Expires in 1 hour (seconds)
path: '/', // Available on all routes
});
常见变体
使用表单提交
// app/PreferencesForm.tsx
'use client';
import { savePreferences } from './actions';
export default function PreferencesForm() {
return (
<form action={savePreferences}>
<label>
<input type="checkbox" name="notifications" />
Enable Notifications
</label>
<button type="submit">Save</button>
</form>
);
}
// app/actions.ts
'use server';
import { cookies } from 'next/headers';
export async function savePreferences(formData: FormData) {
const cookieStore = await cookies();
const notifications = formData.get('notifications') === 'on';
cookieStore.set('notifications', String(notifications), {
httpOnly: true,
maxAge: 60 * 60 * 24 * 365,
});
}
设置 Cookie 后重定向
// app/actions.ts
'use server';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function login(email: string, password: string) {
// Authenticate user
const session = await authenticate(email, password);
// Set session cookie
const cookieStore = await cookies();
cookieStore.set('session', session.token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 1 week
});
// Redirect to dashboard
redirect('/dashboard');
}
为什么使用此模式?
客户端组件不能直接设置 Cookie 吗? 不能。客户端组件在浏览器中运行,现代浏览器出于安全考虑会限制 Cookie 操作。Server Action 在允许设置 Cookie 的服务端运行。
为什么不使用 Route Handler(API 路由)? 可以!但 Server Action 更简单,也与 Next.js App Router 模式集成得更好。
// Alternative: Route Handler approach
// app/api/set-cookie/route.ts
export async function POST(request: Request) {
const { name, value } = await request.json();
return new Response(null, {
status: 200,
headers: {
'Set-Cookie': `${name}=${value}; HttpOnly; Path=/; Max-Age=31536000`,
},
});
}
// Client component
async function setCookie() {
await fetch('/api/set-cookie', {
method: 'POST',
body: JSON.stringify({ name: 'theme', value: 'dark' }),
});
}
推荐使用 Server Action,因为它们:
- 类型安全性更高
- 样板代码更少
- 与表单的集成更好
- 更容易测试
读取 Cookie
在服务端组件中:
// app/page.tsx
import { cookies } from 'next/headers';
export default async function Page() {
const cookieStore = await cookies();
const theme = cookieStore.get('theme')?.value || 'light';
return <div className={theme}>Content</div>;
}
在客户端组件中:
// Can't use next/headers in client components!
// Use document.cookie or a state management library
'use client';
import { useEffect, useState } from 'react';
export default function ThemeDisplay() {
const [theme, setTheme] = useState('light');
useEffect(() => {
// Read from document.cookie
const cookieTheme = document.cookie
.split('; ')
.find(row => row.startsWith('theme='))
?.split('=')[1];
if (cookieTheme) setTheme(cookieTheme);
}, []);
return <div>Current theme: {theme}</div>;
}
快速检查清单
需要在点击按钮时设置 Cookie:
- 创建带有
'use client'的客户端组件 - 添加 onClick 处理程序或表单提交
- 创建 Server Action 文件(例如
app/actions.ts) - 添加
'use server'指令 - 从
next/headers导入cookies - 等待
cookies()完成(Next.js 15+) - 调用
cookieStore.set(name, value, options) - 在客户端组件中导入 Server Action
- 在处理程序中调用 Server Action
总结
客户端-服务端 Cookie 模式:
- ✅ 客户端组件处理用户交互
- ✅ Server Action 设置 Cookie
- ✅ 两个文件:组件 + actions
- ✅ 使用正确的 TypeScript,确保类型安全
- ✅ 安全(httpOnly、secure、sameSite 选项)
此模式是 Next.js App Router 中处理由客户端触发的 Cookie 操作的推荐方式。
评论
支持 Markdown