nextjs-client-cookie-pattern by wsimmonds/claude-nextjs-skills
npx skills add https://github.com/wsimmonds/claude-nextjs-skills --skill nextjs-client-cookie-pattern此模式处理 Next.js 中的一个常见需求:需要设置服务器端 Cookie 的客户端交互(按钮点击)。
为何需要两个文件?
'use client') 可以拥有 onClick 事件处理器场景: 一个点击时设置 Cookie 的按钮
文件 1:客户端组件 (app/CookieButton.tsx)
'use client' 指令文件 2:服务器操作 (app/actions.ts)
'use server' 指令广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
next/headers 的 cookies()// 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>
);
}
// 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 ← 客户端组件
├── actions.ts ← 服务器操作
└── page.tsx ← 使用 CookieButton
any 类型此代码库已启用 @typescript-eslint/no-explicit-any。
// ❌ 错误
async function setCookie(key: any, value: any) { ... }
// ✅ 正确
async function setCookie(key: string, value: string) { ... }
// 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, // 允许客户端读取
maxAge: 60 * 60 * 24 * 365,
});
}
// 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,
});
}
// 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,
});
}
cookieStore.set('name', 'value', {
httpOnly: true, // 防止 JavaScript 访问(安全)
secure: true, // 仅通过 HTTPS 发送
sameSite: 'lax', // CSRF 防护
maxAge: 3600, // 1 小时后过期(秒)
path: '/', // 在所有路由上可用
});
// 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,
});
}
// app/actions.ts
'use server';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function login(email: string, password: string) {
// 验证用户
const session = await authenticate(email, password);
// 设置会话 Cookie
const cookieStore = await cookies();
cookieStore.set('session', session.token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 1 周
});
// 重定向到仪表板
redirect('/dashboard');
}
客户端组件不能直接设置 Cookie 吗? 不能。客户端组件在浏览器中运行,而现代浏览器出于安全考虑限制 Cookie 操作。服务器操作在允许设置 Cookie 的服务器上运行。
为什么不使用路由处理器(API 路由)? 可以!但服务器操作更简单,并且与 Next.js App Router 模式集成得更好。
// 替代方案:路由处理器方法
// 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`,
},
});
}
// 客户端组件
async function setCookie() {
await fetch('/api/set-cookie', {
method: 'POST',
body: JSON.stringify({ name: 'theme', value: 'dark' }),
});
}
服务器操作是首选,因为它们:
在服务器组件中:
// 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>;
}
在客户端组件中:
// 不能在客户端组件中使用 next/headers!
// 使用 document.cookie 或状态管理库
'use client';
import { useEffect, useState } from 'react';
export default function ThemeDisplay() {
const [theme, setTheme] = useState('light');
useEffect(() => {
// 从 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' 的客户端组件app/actions.ts)'use server' 指令next/headers 导入 cookiescookies()(Next.js 15+)cookieStore.set(name, value, options)客户端-服务器 Cookie 模式:
此模式是在 Next.js App Router 中处理客户端触发的 Cookie 操作的推荐方式。
每周安装次数
121
代码仓库
GitHub 星标数
80
首次出现
2026年1月23日
安全审计
安装于
opencode97
claude-code96
codex95
github-copilot92
gemini-cli92
cursor88
This pattern handles a common Next.js requirement: client-side interaction (button click) that needs to set server-side cookies.
Why Two Files?
'use client') can have onClick handlersScenario: A button that sets a cookie when clicked
File 1: Client Component (app/CookieButton.tsx)
'use client' directiveFile 2: Server Action (app/actions.ts)
'use server' directivecookies() from next/headers// 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>
);
}
// 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
any TypeThis codebase has @typescript-eslint/no-explicit-any enabled.
// ❌ WRONG
async function setCookie(key: any, value: any) { ... }
// ✅ CORRECT
async function setCookie(key: string, value: string) { ... }
// 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,
});
}
// 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,
});
}
// 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,
});
}
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,
});
}
// 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');
}
Can't client components set cookies directly? No. Client components run in the browser, and modern browsers restrict cookie manipulation for security. Server actions run on the server where cookie-setting is allowed.
Why not use a Route Handler (API route)? You can! But server actions are simpler and more integrated with the Next.js App Router pattern.
// 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 actions are preferred because they're:
In Server Components:
// 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>;
}
In Client Components:
// 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>;
}
When you need to set cookies from a button click:
'use client'app/actions.ts)'use server' directivecookies from next/headerscookies() (Next.js 15+)cookieStore.set(name, value, options)Client-Server Cookie Pattern:
This pattern is the recommended way to handle client-triggered cookie operations in Next.js App Router.
Weekly Installs
121
Repository
GitHub Stars
80
First Seen
Jan 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode97
claude-code96
codex95
github-copilot92
gemini-cli92
cursor88
Vue 3 调试指南:解决响应式、计算属性与监听器常见错误
11,800 周安装
Unsloth开发助手 - 基于官方文档的AI模型微调与代码生成技能
228 周安装
Git变更日志生成器:自动将技术提交转为用户友好的发布说明和更新摘要
226 周安装
TensorRT-LLM:NVIDIA GPU大语言模型推理优化库,实现24K tokens/秒超高吞吐量
228 周安装
Telegram Mini App开发指南:集成TON区块链、Web App API与React框架构建应用
229 周安装
临床决策支持(CDS)文档生成工具 - 制药研发与循证医学指南
230 周安装
Amazon产品查找器 - 为联盟营销快速寻找最佳亚马逊产品的AI工具
230 周安装