nextjs-client-cookie-pattern

安装量: 103
排名: #8125

安装

npx skills add https://github.com/wsimmonds/claude-nextjs-skills --skill nextjs-client-cookie-pattern

Next.js: Client Component + Server Action Cookie Pattern Pattern Overview

This pattern handles a common Next.js requirement: client-side interaction (button click) that needs to set server-side cookies.

Why Two Files?

Client components ('use client') can have onClick handlers Only server code can set cookies (security requirement) Solution: Client component calls a server action that sets cookies The Pattern

Scenario: A button that sets a cookie when clicked

File 1: Client Component (app/CookieButton.tsx)

Has 'use client' directive Has onClick handler Imports and calls server action

File 2: Server Action (app/actions.ts)

Has 'use server' directive Uses cookies() from next/headers Sets the cookie Complete Implementation File 1: Client Component // app/CookieButton.tsx 'use client';

import { setPreference } from './actions';

export default function CookieButton() { const handleClick = async () => { await setPreference('dark-mode', 'true'); };

return ( ); }

File 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 }); }

File Structure app/ ├── CookieButton.tsx ← Client component ├── actions.ts ← Server actions └── page.tsx ← Uses CookieButton

TypeScript: NEVER Use any Type

This codebase has @typescript-eslint/no-explicit-any enabled.

// ❌ WRONG async function setCookie(key: any, value: any) { ... }

// ✅ CORRECT async function setCookie(key: string, value: string) { ... }

Real-World Examples Example 1: Theme Toggle // 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 ( ); }

// 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, }); }

Example 2: Accept Cookies Banner // 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 (

We use cookies to improve your experience.

); }

// 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, }); }

Example 3: Language Preference // app/LanguageSelector.tsx 'use client';

import { setLanguage } from './actions';

export default function LanguageSelector() { const languages = ['en', 'es', 'fr', 'de'];

return ( ); }

// 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 Options 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 });

Common Variations With Form Submission // app/PreferencesForm.tsx 'use client';

import { savePreferences } from './actions';

export default function PreferencesForm() { return (

); }

// 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, }); }

With Redirect After Setting 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'); }

Why This Pattern?

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:

More type-safe Less boilerplate Better integrated with forms Easier to test Reading Cookies

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

Content
; }

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

Current theme: {theme}
; }

Quick Checklist

When you need to set cookies from a button click:

Create client component with 'use client' Add onClick handler or form submission Create server action file (e.g., app/actions.ts) Add 'use server' directive Import cookies from next/headers Await cookies() (Next.js 15+) Call cookieStore.set(name, value, options) Import server action in client component Call server action from handler Summary

Client-Server Cookie Pattern:

✅ Client component handles user interaction ✅ Server action sets the cookie ✅ Two files: component + actions ✅ Type-safe with proper TypeScript ✅ Secure (httpOnly, secure, sameSite options)

This pattern is the recommended way to handle client-triggered cookie operations in Next.js App Router.

返回排行榜