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.
Server-Side Cookie Management
Next.js 13+ makes server-side cookie handling straightforward with built-in methods.
Reading Cookies in Server Components
// 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
// 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
// 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>
);
}
Client-Side Cookie Management
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.
Installing js-cookie
npm install js-cookie
Reading and Writing Cookies in Client Components
// 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>
);
}
Cookie Options and Security
When setting cookies, it's important to understand the available options:
Option | Description |
---|---|
httpOnly | Prevents client-side JavaScript from accessing the cookie |
secure | Cookie is only sent over HTTPS |
maxAge | Maximum age of the cookie in seconds |
expires | Expiration date of the cookie |
path | Path where the cookie is valid |
domain | Domain where the cookie is valid |
sameSite | Controls when cookies are sent with cross-site requests (strict , lax , or none ) |
Security Best Practices
- Use
httpOnly
for sensitive data: This prevents access via client-side JavaScript, protecting against XSS attacks - Enable
secure
flag: Ensures cookies are only sent over HTTPS - Implement
sameSite
controls: Usestrict
orlax
to protect against CSRF attacks - Limit cookie lifespan: Set reasonable expiration times
- 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
// 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
// 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
// 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
// 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:
// 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 Management | Best For | Limitations |
---|---|---|
Cookies | Authentication, user preferences, small data needs | Limited size (4KB), sent with every request |
Local Storage | Larger client-side data, non-sensitive information | Client-side only, no automatic expiration |
Session Storage | Temporary session data | Cleared when tab closes, client-side only |
Server-Side State | Sensitive data, complex state management | Requires 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
- Create a dark mode toggle that persists user preference using cookies
- Implement a "remember me" feature for a login form using cookies
- Build a language selector that stores the user's preferred language
- Create a banner that appears only once by setting a cookie after it's dismissed
- 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! :)