Skip to main content

Next.js Cookies

In web development, cookies are small pieces of data stored in the user's browser that can be used to maintain state between requests. When building Next.js applications, cookies provide a convenient way to persist user preferences, authentication tokens, and other data that needs to survive across page refreshes.

Understanding Cookies in Next.js

Next.js has evolved how we work with cookies across its versions, particularly with the introduction of the App Router. What makes Next.js cookies unique is how they can be accessed and manipulated in both server and client components.

Why Use Cookies for State Management?

Cookies offer several advantages for state management:

  • Persistence: Data survives page refreshes and browser sessions
  • Server-Side Access: Can be read directly in server components
  • Size Efficiency: Cookies are automatically sent with every request, so they're best kept small
  • Security Options: Can be configured to enhance security (HTTP-only, Secure, SameSite)

Working with Cookies in Next.js

Let's explore how to handle cookies in different contexts within a Next.js application.

Next.js 13+ makes server-side cookie handling straightforward with built-in methods.

Reading Cookies in Server Components

jsx
// app/profile/page.js
import { cookies } from 'next/headers';

export default function ProfilePage() {
const cookieStore = cookies();
const theme = cookieStore.get('theme')?.value || 'light';

return (
<div>
<h1>User Profile</h1>
<p>Your current theme preference: {theme}</p>
</div>
);
}

Setting and Removing Cookies in Server Actions

jsx
// app/actions.js
'use server'

import { cookies } from 'next/headers';

export async function setThemePreference(theme) {
cookies().set('theme', theme, {
httpOnly: true,
path: '/',
maxAge: 60 * 60 * 24 * 30, // 30 days
sameSite: 'strict'
});

return { success: true };
}

export async function clearThemePreference() {
cookies().delete('theme');
return { success: true };
}

Using Server Actions with Forms

jsx
// app/theme-selector.js
'use client'

import { setThemePreference } from './actions';

export default function ThemeSelector() {
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const theme = formData.get('theme');

await setThemePreference(theme);
window.location.reload(); // Refresh to see changes
}

return (
<form onSubmit={handleSubmit}>
<select name="theme">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
<button type="submit">Save Preference</button>
</form>
);
}

While Next.js provides server-side cookie utilities, you'll often need to work with cookies on the client side. For this, we can use the popular js-cookie library.

bash
npm install js-cookie

Reading and Writing Cookies in Client Components

jsx
// app/components/ClientThemeSelector.js
'use client'

import { useState, useEffect } from 'react';
import Cookies from 'js-cookie';

export default function ClientThemeSelector() {
const [theme, setTheme] = useState('light');

useEffect(() => {
// Get initial theme from cookie when component mounts
const savedTheme = Cookies.get('theme');
if (savedTheme) {
setTheme(savedTheme);
}
}, []);

const handleThemeChange = (newTheme) => {
setTheme(newTheme);
// Save to cookie
Cookies.set('theme', newTheme, { expires: 30 }); // expires in 30 days

// Apply theme to the page (example)
document.documentElement.dataset.theme = newTheme;
};

return (
<div>
<h2>Select Theme</h2>
<div className="theme-buttons">
<button
onClick={() => handleThemeChange('light')}
className={theme === 'light' ? 'active' : ''}
>
Light
</button>
<button
onClick={() => handleThemeChange('dark')}
className={theme === 'dark' ? 'active' : ''}
>
Dark
</button>
</div>
</div>
);
}

When setting cookies, it's important to understand the available options:

OptionDescription
httpOnlyPrevents client-side JavaScript from accessing the cookie
secureCookie is only sent over HTTPS
maxAgeMaximum age of the cookie in seconds
expiresExpiration date of the cookie
pathPath where the cookie is valid
domainDomain where the cookie is valid
sameSiteControls when cookies are sent with cross-site requests (strict, lax, or none)

Security Best Practices

  1. Use httpOnly for sensitive data: This prevents access via client-side JavaScript, protecting against XSS attacks
  2. Enable secure flag: Ensures cookies are only sent over HTTPS
  3. Implement sameSite controls: Use strict or lax to protect against CSRF attacks
  4. Limit cookie lifespan: Set reasonable expiration times
  5. Minimize cookie size: Keep them under 4KB and store only essential information

Practical Example: Authentication System

Let's implement a simple authentication system using cookies in Next.js:

Server Component for Auth Status

jsx
// app/components/AuthStatus.js
import { cookies } from 'next/headers';

export default function AuthStatus() {
const cookieStore = cookies();
const isLoggedIn = cookieStore.has('authToken');

return (
<div className="auth-status">
{isLoggedIn ? (
<p>You are logged in! 🔒</p>
) : (
<p>Please log in to continue. 🔑</p>
)}
</div>
);
}

Server Action for Login/Logout

jsx
// app/actions/auth.js
'use server'

import { cookies } from 'next/headers';

export async function login(username, password) {
// In a real app, you would validate credentials against a database
if (username === 'user' && password === 'password') {
// Create a token (in a real app, generate a proper JWT)
const token = `user_${Date.now()}_token`;

cookies().set('authToken', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24, // 1 day
path: '/',
sameSite: 'lax',
});

return { success: true };
}

return { success: false, error: 'Invalid credentials' };
}

export async function logout() {
cookies().delete('authToken');
return { success: true };
}

Client Login Form

jsx
// app/components/LoginForm.js
'use client'

import { useState } from 'react';
import { login } from '../actions/auth';

export default function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');

async function handleSubmit(event) {
event.preventDefault();
setError('');

const result = await login(username, password);

if (result.success) {
window.location.reload();
} else {
setError(result.error);
}
}

return (
<form onSubmit={handleSubmit} className="login-form">
<h2>Login</h2>

{error && <div className="error">{error}</div>}

<div className="form-group">
<label htmlFor="username">Username:</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>

<div className="form-group">
<label htmlFor="password">Password:</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>

<button type="submit">Login</button>
</form>
);
}

Logout Button

jsx
// app/components/LogoutButton.js
'use client'

import { logout } from '../actions/auth';

export default function LogoutButton() {
async function handleLogout() {
await logout();
window.location.reload();
}

return (
<button onClick={handleLogout} className="logout-button">
Log Out
</button>
);
}

Using Cookies with Server-Side Redirects

A common pattern is to check for authentication and redirect users:

jsx
// app/dashboard/page.js
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

export default function DashboardPage() {
const cookieStore = cookies();
const authToken = cookieStore.get('authToken');

// If not authenticated, redirect to login page
if (!authToken) {
redirect('/login');
}

return (
<div className="dashboard">
<h1>Dashboard</h1>
<p>Welcome to your protected dashboard!</p>
{/* Dashboard contents */}
</div>
);
}

Cookies vs. Other State Management Options

When should you use cookies instead of alternatives?

State ManagementBest ForLimitations
CookiesAuthentication, user preferences, small data needsLimited size (4KB), sent with every request
Local StorageLarger client-side data, non-sensitive informationClient-side only, no automatic expiration
Session StorageTemporary session dataCleared when tab closes, client-side only
Server-Side StateSensitive data, complex state managementRequires database, more complex setup

Summary

Cookies provide a powerful tool for state management in Next.js applications:

  • They can be accessed and modified on both the server and client sides
  • They're ideal for authentication, user preferences, and other small pieces of persistent data
  • Next.js provides convenient APIs through next/headers for server components
  • Client-side manipulation requires a library like js-cookie
  • Security considerations are important when working with cookies

By understanding when and how to use cookies in your Next.js applications, you can implement persistent state that works seamlessly across both server and client components.

Exercises

  1. Create a dark mode toggle that persists user preference using cookies
  2. Implement a "remember me" feature for a login form using cookies
  3. Build a language selector that stores the user's preferred language
  4. Create a banner that appears only once by setting a cookie after it's dismissed
  5. Implement a shopping cart that persists items between sessions using cookies

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)