Let me tell you a story about how I once spent an entire afternoon debugging why my user preferences weren't persisting, only to discover I'd set a cookie with path=/admin
while my app was running at path=/
. That was the day I vowed to master cookie management in Next.js, and today, I'm going to share that hard-earned knowledge with you.
๐ง Why Bother With Proper Cookie Management?
Cookies are like the breadcrumbs of the web (hence the terrible pun in this post's title). They help us remember user preferences, maintain sessions, and keep our applications stateful. But much like real breadcrumbs, if you don't manage them properly, you'll end up with a mess.
๐ ๏ธ The Building Blocks
Here are the two files you'll need to implement in your project:
lib/cookies.ts
- The Core Utility
export type CookieOptions = {
days?: number;
path?: string;
domain?: string;
secure?: boolean;
sameSite?: "Lax" | "Strict" | "None";
};
export class ClientCookies {
/**
* Set a cookie
* @param name Cookie name
* @param value Cookie value
* @param options Cookie options
*/
static set(name: string, value: string, options: CookieOptions = {}) {
if (typeof window === "undefined") return;
const {
days = 30,
path = "/",
domain,
secure = window.location.protocol === "https:",
sameSite = "Lax",
} = options;
let cookieString = `${name}=${encodeURIComponent(value)}`;
if (days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
cookieString += `; expires=${date.toUTCString()}`;
}
cookieString += `; path=${path}`;
if (domain) cookieString += `; domain=${domain}`;
if (secure) cookieString += "; secure";
if (sameSite) cookieString += `; sameSite=${sameSite}`;
document.cookie = cookieString;
}
/**
* Get a cookie value by name
* @param name Cookie name
* @returns Cookie value or null if not found
*/
static get(name: string): string | null {
if (typeof window === "undefined") return null;
const cookies = document.cookie.split("; ");
for (const cookie of cookies) {
const [cookieName, cookieValue] = cookie.split("=");
if (cookieName === name) {
return decodeURIComponent(cookieValue);
}
}
return null;
}
/**
* Delete a cookie
* @param name Cookie name
* @param path Cookie path
* @param domain Cookie domain
*/
static delete(name: string, path = "/", domain?: string) {
if (typeof window === "undefined") return;
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}${
domain ? `; domain=${domain}` : ""
}`;
}
/**
* Check if a cookie exists
* @param name Cookie name
* @returns boolean indicating if cookie exists
*/
static has(name: string): boolean {
if (typeof window === "undefined") return false;
return document.cookie
.split("; ")
.some(cookie => cookie.split("=")[0] === name);
}
}
Key Features:
- ๐ SSR-safe (won't break during server-side rendering)
- ๐ก๏ธ Secure defaults (auto HTTPS detection)
- ๐ฆ Comprehensive cookie options
- ๐งน Clean API for all cookie operations
hooks/use-cookies.ts
- The React Hook
import { useEffect, useState } from "react";
import { ClientCookies, CookieOptions } from "@/lib/cookies";
type UseCookieReturn = {
value: string | null;
set: (value: string, options?: CookieOptions) => void;
remove: () => void;
has: boolean;
};
export const useCookie = (
name: string,
defaultValue?: string,
): UseCookieReturn => {
const [cookieValue, setCookieValue] = useState<string | null>(() => {
return ClientCookies.get(name) ?? defaultValue ?? null;
});
const set = (value: string, options?: CookieOptions) => {
ClientCookies.set(name, value, options);
setCookieValue(value);
};
const remove = () => {
ClientCookies.delete(name);
setCookieValue(null);
};
const has = ClientCookies.has(name);
// Sync cookie changes between browser tabs
useEffect(() => {
const handler = () => {
setCookieValue(ClientCookies.get(name) ?? defaultValue ?? null);
};
window.addEventListener("storage", handler);
return () => window.removeEventListener("storage", handler);
}, [name, defaultValue]);
return { value: cookieValue, set, remove, has };
};
Key Features:
- โป๏ธ Reactive cookie values
- ๐ Automatic cross-tab synchronization
- ๐ฃ Simple hook interface
- โก Optimized performance
๐ช Setting Cookies Like a Pro
Here's how to use our cookie system in your components:
// Using the ClientCookies directly
ClientCookies.set("user_token", "abc123", {
days: 7,
secure: true,
sameSite: "Strict"
});
// Using the hook in a component
function UserPreferences() {
const { value: theme, set: setTheme } = useCookie("theme", "light");
const toggleTheme = () => {
setTheme(theme === "light" ? "dark" : "light", { days: 30 });
};
return (
<button onClick={toggleTheme}>
Switch to {theme === "light" ? "dark ๐" : "light โ๏ธ"} mode
</button>
);
}
๐ Cookie Inspection Made Easy
Check cookies with these simple methods:
// Check if cookie exists
if (ClientCookies.has("user_consent")) {
loadAnalytics();
}
// Get cookie value
const userId = ClientCookies.get("user_id");
// With hook
const { value: userId, has: hasUserId } = useCookie("user_id");
๐๏ธ Deleting Cookies Properly
Don't just forget about cookies - remove them correctly:
// Direct method
ClientCookies.delete("temp_session");
// Using hook
const { remove } = useCookie("auth_token");
remove();
๐ Real-World Use Cases
1. Authentication Flow ๐
// After login
ClientCookies.set("auth_token", response.token, {
days: 1,
secure: true,
sameSite: "Strict"
});
// In protected component
const { value: token } = useCookie("auth_token");
// On logout
const { remove } = useCookie("auth_token");
remove();
2. User Preferences โ๏ธ
function FontSizeSelector() {
const { value: fontSize, set: setFontSize } = useCookie("font_size", "16");
return (
<select
value={fontSize}
onChange={(e) => setFontSize(e.target.value, { days: 365 })}
>
<option value="14">Small</option>
<option value="16">Medium</option>
<option value="18">Large</option>
</select>
);
}
3. GDPR Consent ๐๏ธ
function CookieBanner() {
const { value: consent, set: setConsent } = useCookie("cookie_consent");
if (consent) return null;
return (
<div className="banner">
<p>We use cookies ๐ช</p>
<button onClick={() => setConsent("accepted", { days: 365 })}>
Accept
</button>
</div>
);
}
๐ Advanced Features
Cross-Tab Synchronization
Our useCookie
hook automatically syncs cookie changes across browser tabs using the storage
event. No more stale data!
// Inside useCookie hook
useEffect(() => {
const handler = () => {
setCookieValue(ClientCookies.get(name) ?? defaultValue ?? null);
};
window.addEventListener("storage", handler);
return () => window.removeEventListener("storage", handler);
}, [name, defaultValue]);
Object Storage
While cookies only store strings, we can easily handle objects:
// Store object
const userSettings = { darkMode: true, notifications: false };
ClientCookies.set("settings", JSON.stringify(userSettings));
// Retrieve
const settings = JSON.parse(ClientCookies.get("settings") || "{}");
// With hook
const { value: settings } = useCookie("settings");
const parsedSettings = JSON.parse(settings || "{}");
๐ Troubleshooting Tips
If your cookies aren't behaving:
- Check the path ๐ - Is it set correctly?
- Verify secure flag ๐ - Especially in production
- Inspect cookie in browser ๐ - Chrome DevTools > Application > Cookies
- Test cross-tab behavior โ๏ธ - Change in one tab, check another
Now go forth and manage those cookies like a pro! Just remember, unlike real cookies, these ones won't make you gain weight. ๐๏ธโโ๏ธ
Pro Tip: Keep your actual cookies (the edible ones) away from your keyboard while coding. I speak from experience. ๐ช๐ป
Top comments (0)