У попередніх розділах ми дослідили нативний інтерфейс document.cookie. Ви напевно помітили, наскільки він архаїчний та незручний. Робота з ним нагадує спробу написати SMS, використовуючи азбуку Морзе — це можливо, але навіщо так страждати у 21-му столітті?
Сьогодні ми познайомимось із js-cookie — бібліотекою, яка перетворює роботу з куками з "каторги" на елегантний та зрозумілий процес. Це Industry Standard для клієнтської роботи з cookies.
Перш ніж пірнати в рішення, згадаймо проблему. Чому розробники тікають від document.cookie?
Уявіть, що ви хочете просто прочитати куку user_id. Нативний спосіб виглядає як регулярний вираз або цикл з розбиттям рядка:
// Native Way (The "Painful" Way)
const getCookie = (name) => {
const value = `; ${document.cookie}`
const parts = value.split(`; ${name}=`)
if (parts.length === 2) return parts.pop().split(';').shift()
}
Це багато коду для такої простої дії. А як щодо запису?
// Запис складної куки
document.cookie = 'user=John%20Doe; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT; Secure; SameSite=Strict'
Тут легко помилитися в форматі дати, забути encodeURIComponent, або переплутати порядок.
js-cookie вирішує це радикально просто:
Cookies.get('user_id') // Просто і гарно
Оскільки ми використовуємо Vite як наш сучасний збирач проєктів, встановлення відбудеться через NPM.
Відкрийте термінал у вашому проєкті та виконайте команду:
npm install js-cookie
yarn add js-cookie
pnpm add js-cookie
Якщо ви використовуєте TypeScript (а ви, як професіонал, маєте прагнути до цього), обов'язково встановіть типи. Бібліотека написана на JS, тому типи поставляються окремо:
npm install --save-dev @types/js-cookie
У вашому JavaScript/TypeScript файлі:
import Cookies from 'js-cookie'
console.log(Cookies.get()) // Перевірка роботи
Бібліотека надає інтуїтивний API, який нагадує роботу з localStorage.
Метод Cookies.set(name, value, [options]) використовується для створення або оновлення куки.
// Найпростіший випадок
// Створює сесійну куку для всього сайту
Cookies.set('theme', 'dark')
Анатомія виклику:
'theme': Назва куки (кей).'dark': Значення. Воно автоматично енкодиться (URL-encoded).!TIP js-cookie автоматично перетворить
spaceна%20та інші спецсимволи. Вам не потрібно викликатиencodeURIComponent.
Метод Cookies.get([name]) дозволяє отримати значення.
// Отримання конкретної куки
const currentTheme = Cookies.get('theme') // => 'dark'
// Отримання неіснуючої куки
const token = Cookies.get('auth_token') // => undefined
// Отримання ВСІХ доступних кук (повертає об'єкт)
const allCookies = Cookies.get()
// => { theme: 'dark', _ga: 'GA1.2.345...' }
Важливий момент: При читанні бібліотека автоматично декодує значення (decodeURIComponent).
Метод Cookies.remove(name, [options]) видаляє куку.
Cookies.remove('theme')
path або domain, ви МУСИТЕ передати ті самі параметри при видаленні!// Встановлення
Cookies.set('id', '123', { path: '/dashboard' })
// Не спрацює! (шукає в path: '/')
Cookies.remove('id')
// Спрацює
Cookies.remove('id', { path: '/dashboard' })
Одна з найпотужніших фіч js-cookie — прозора робота з JSON об'єктами. Нативний API приймає лише рядки. Якщо вам треба зберегти налаштування користувача, вам доводиться робити JSON.stringify. js-cookie бере це на себе.
const userSettings = {
theme: 'dark',
notifications: false,
sidebar: 'collapsed',
}
// Автоматичний JSON.stringify під капотом
Cookies.set('settings', userSettings)
У браузері це буде збережено як закодований JSON рядок.
// Автоматичний JSON.parse
const settings = Cookies.getJSON('settings')
// або в нових версіях просто get, якщо бібліотека детектує JSON,
// але краще явно контролювати це.
Примітка: У версії 3.x метод getJSON був видалений для спрощення API. Тепер розробник має сам дбати про парсинг, або використовувати конвертери (про це далі).
Давайте подивимось, як це робити правильно у v3+ (поточний стандарт):
// v3 Pattern
const value = Cookies.get('settings')
if (value) {
try {
const settings = JSON.parse(value)
console.log(settings.theme) // 'dark'
} catch (e) {
console.error('Cookie parsing failed', e)
}
}
Чекайте, це ж знову незручно! Саме тому в v3 з'явилися Converters (Конвертери), які дозволяють повернути магію.
Конвертери дозволяють змінити поведінку читання та запису за замовчуванням. Ми можемо створити свій екземпляр API, який автоматично працює з JSON.
import Cookies from 'js-cookie'
const JsonCookies = Cookies.withConverter({
read: function (value, name) {
// Якщо значення починається з 'j:', це наш маркер JSON (опціонально)
// Або просто пробуємо парсити все
try {
return JSON.parse(value)
} catch (e) {
// Якщо не вийшло, повертаємо як рядок
return value
}
},
write: function (value, name) {
// Якщо це об'єкт, стрінгіфаємо його
if (typeof value === 'object') {
return JSON.stringify(value)
}
return value
},
})
// Використання
JsonCookies.set('config', { color: 'red' })
const config = JsonCookies.get('config') // => { color: 'red' }
Це демонструє архітектурну гнучкість бібліотеки. Ви не прив'язані до дефолтної поведінки.
Cookies — це не просто ключ-значення. Це механізм з купою налаштувань безпеки та життєвого циклу. Всі вони передаються третім аргументом.
За замовчуванням кука є Session Cookie (зникає при закритті браузера). Щоб зробити її постійною, треба вказати expires.
js-cookie приймає кількість днів як число (на відміну від нативного API, де треба передавати об'єкт Date у форматі UTC String).
// Живе 7 днів
Cookies.set('token', 'xyz', { expires: 7 })
// Живе півдня (0.5 дня = 12 годин)
Cookies.set('short_lived', 'value', { expires: 0.5 })
// Живе 100 років (фактично вічна)
Cookies.set('forever', 'value', { expires: 36500 })
Якщо вам все ж потрібна точність до секунди, ви можете передати об'єкт Date:
const date = new Date()
date.setTime(date.getTime() + 15 * 60 * 1000) // +15 хвилин
Cookies.set('session', 'active', { expires: date })
Визначає, на яких URL доступна кука.
'/' (доступна на всьому сайті).// Доступна тільки на /store і вкладених (/store/cart)
Cookies.set('cart_id', '555', { path: '/store' })
Дозволяє шарити куки між піддоменами.
Уявіть, що у вас є app.example.com і blog.example.com.
За замовчуванням кука з app. не видна на blog..
// Робимо куку доступною для *.example.com
Cookies.set('sso_token', 'token_val', { domain: '.example.com' })
Критично для безпеки. Браузер не віддасть цю куку, якщо з'єднання не захищене (HTTP).
Cookies.set('secret', 'top_secret', { secure: true })
!IMPORTANT Завжди використовуйте
secure: trueдля авторизаційних токенів та чутливих даних у продакшн середовищі. Наlocalhost(крім Chrome) це може працювати і без HTTPS, але в Інтернеті — обов'язково.
Атрибут, який контролює, коли куки відправляються при крос-сайтових запитах.
'Strict': Кука відправляється тільки якщо ви на тому ж сайті. Ніяких лінків з пошти.'Lax': (Дефолт у сучасних браузерах) Дозволяє безпечні переходи (GET) з інших сайтів.'None': Відправляється завжди (потрібен Secure: true).Cookies.set('auth', 'token', { sameSite: 'Strict', secure: true })
Якщо ви хочете, щоб усі ваші куки мали певні атрибути (наприклад, SameSite: Strict), не треба писати це щоразу. Використовуйте withAttributes:
const SecureCookies = Cookies.withAttributes({
path: '/',
secure: true,
parse: 'Strict',
})
SecureCookies.set('a', '1') // вже має secure і path
SecureCookies.set('b', '2')
Давайте реалізуємо класичний функціонал "Remember Me" на сторінці логіну.
Задача: Користувач вводить логін. Якщо він ставить галочку "Запам'ятати", ми зберігаємо його email на 30 днів.
<form id="loginForm">
<input type="email" id="email" placeholder="Email" />
<label> <input type="checkbox" id="remember" /> Запам'ятати мене </label>
<button type="submit">Увійти</button>
</form>
import Cookies from 'js-cookie'
const emailInput = document.getElementById('email')
const rememberCheckbox = document.getElementById('remember')
const form = document.getElementById('loginForm')
// 1. При завантаженні перевіряємо куку
const savedEmail = Cookies.get('remember_email')
if (savedEmail) {
emailInput.value = savedEmail
rememberCheckbox.checked = true
}
// 2. Обробка сабміту
form.addEventListener('submit', (e) => {
e.preventDefault()
const email = emailInput.value
const shouldRemember = rememberCheckbox.checked
if (shouldRemember) {
// Зберігаємо на 30 днів
Cookies.set('remember_email', email, { expires: 30, sameSite: 'Lax' })
} else {
// Якщо галочку зняли — видаляємо куку
Cookies.remove('remember_email')
}
// ... далі логіка авторизації
console.log('Logging in...')
})
Розбір Коду:
.remove(), ми чистимо "хвости".Щоб використовувати інструмент професійно, треба розуміти, як він працює всередині. js-cookie — це не просто обгортка над document.cookie, це розумний механізм, який вирішує проблеми сумісності та стандартів.
Згідно зі стандартом RFC 6265, значення куки може містити обмежений набір символів. Пробіли, коми, крапки з комою та Non-ASCII символи заборонені або можуть інтерпретуватися неправильно різними браузерами.
Як це робить js-cookie:
Коли ви викликаєте Cookies.set('key', 'val ue'), бібліотека автоматично застосовує encodeURIComponent до значення (але не до всіх символів!).
Вона використовує м'яке кодування:
! # $ & ' ( ) * + - . / : < = > ? @ [ ] ^ _ { } | ~.% (процент), ; (сепаратор), , (кома у старих браузерах), \ (бексліш) та контрольні символи.Це робить куки максимально читабельними для сервера, на відміну від повного encodeURIComponent, який перетворив би все на кашу з відсотків.
// js-cookie
Cookies.set('a', '{b:c}')
// Результат в браузері: a=%7Bb%3Ac%7D
// (фігурні дужки кодуються, двокрапка - ні, якщо це дозволено)
При читанні Cookies.get('key'), бібліотека бере сирий рядок document.cookie, розбиває його по ; і шукає потрібну пару.
Алгоритм пошуку:
decodeURIComponent).Зверніть увагу: якщо значення куки було змінено іншим скриптом або сервером без правильного кодування, js-cookie може не розпізнати його або повернути raw value (залежить від конвертера).
Використовувати глобальний об'єкт Cookies напряму у великому проєкті — це моветон. Краще створити шар абстракції (Wrapper).
Створимо сервіс, який гарантує, що ми використовуємо правильні ключі.
import Cookies from 'js-cookie'
// 1. Визначаємо дозволені ключі
type CookieKeys = 'auth_token' | 'user_theme' | 'gdpr_consent'
// 2. Створюємо типізовані опції
interface CookieOptions {
expires?: number | Date
path?: string
domain?: string
secure?: boolean
sameSite?: 'Strict' | 'Lax' | 'None'
}
export const CookieService = {
// Обгортка над set
set(key: CookieKeys, value: string, options?: CookieOptions) {
Cookies.set(key, value, {
path: '/',
secure: location.protocol === 'https:', // автоматично secure для https
...options,
})
},
// Обгортка над get
get(key: CookieKeys): string | undefined {
return Cookies.get(key)
},
// Обгортка над remove
remove(key: CookieKeys, options?: CookieOptions) {
Cookies.remove(key, { path: '/', ...options })
},
}
// Використання
CookieService.set('user_theme', 'dark') // OK
// CookieService.set('wrong_key', 'val'); // Error: Argument of type '"wrong_key"' is not assignable...
Переваги:
path: '/') задаються в одному місці.Якщо на одному домені живе кілька додатків (наприклад, admin і shop), корисно додавати префікси до кук, щоб уникнути колізій.
Можна реалізувати це через кастомний конвертер, але простіше через обгортку:
const APP_PREFIX = 'myapp_'
const AppCookies = {
set: (key, val, opts) => Cookies.set(APP_PREFIX + key, val, opts),
get: (key) => Cookies.get(APP_PREFIX + key),
remove: (key, opts) => Cookies.remove(APP_PREFIX + key, opts),
}
AppCookies.set('token', '123')
// Браузер: myapp_token=123
Робота з куками на клієнті має свої обмеження безпеки.
Ви НЕ МОЖЕТЕ прочитати HttpOnly куки через js-cookie (або будь-який JS). Це зроблено навмисно.
document.cookie його не покаже.HttpOnly куку, Cookies.get('token') поверне undefined. Це норма!Будь-яка кука, доступна через JS (HttpOnly: false), може бути вкрадена, якщо на вашому сайті є XSS вразливість.
Рекомендації:
Secure: true для всіх важливих кук.SameSite: Strict або Lax для захисту від CSRF.Давайте створимо повноцінний компонент логіки для банера згоду на використання кук. Це вимога закону в ЄС та багатьох інших регіонах.
Логіка:
cookie_consent.import Cookies from 'js-cookie'
const CONSENT_COOKIE = 'cookie_consent_status'
export class ConsentManager {
constructor(bannerElement) {
this.banner = bannerElement
this.btnAccept = this.banner.querySelector('#acceptCookies')
this.btnReject = this.banner.querySelector('#rejectCookies')
this.init()
}
init() {
// Перевіряємо статус
const status = Cookies.get(CONSENT_COOKIE)
if (!status) {
this.showBanner()
} else {
console.log(`Consent status: ${status}`)
this.applyConsent(status)
}
// Біндимо події
this.btnAccept?.addEventListener('click', () => this.handleConsent('granted'))
this.btnReject?.addEventListener('click', () => this.handleConsent('denied'))
}
showBanner() {
this.banner.classList.remove('hidden')
}
hideBanner() {
this.banner.classList.add('hidden')
}
handleConsent(status) {
// Зберігаємо вибір на 365 днів
Cookies.set(CONSENT_COOKIE, status, {
expires: 365,
sameSite: 'Lax',
secure: true,
})
this.hideBanner()
this.applyConsent(status)
}
applyConsent(status) {
if (status === 'granted') {
this.enableAnalytics()
} else {
this.disableAnalytics()
}
}
enableAnalytics() {
console.log('Google Analytics/Pixel loaded...')
// Тут код завантаження скриптів аналітики
}
disableAnalytics() {
console.log('Analytics disabled respecting user choice.')
// Видаляємо аналітичні куки, якщо вони були
Cookies.remove('_ga')
Cookies.remove('_gid')
}
}
Цей клас капсулює всю логіку і робить ваш код чистим.
js-cookie — це ванільна JS бібліотека, але в екосистемі компонентів ми хочемо реактивність. Давайте напишемо обгортки (Wrappers).
useCookieМи хочемо хук, який не просто читає куку, а й оновлює компонент при її зміні.
Застереження: js-cookie не має івентів зміни кук. Ми можемо реагувати лише на зміни, зроблені через наш хук, або використовувати setInterval (що погано). Найкращий паттерн — це єдине джерело правди.
import { useState, useCallback } from 'react'
import Cookies from 'js-cookie'
export default function useCookie(name: string, defaultValue: string) {
const [value, setValue] = useState<string>(() => {
const cookie = Cookies.get(name)
return cookie || defaultValue
})
const updateCookie = useCallback(
(newValue: string, options?: Cookies.CookieAttributes) => {
Cookies.set(name, newValue, options)
setValue(newValue)
},
[name],
)
const deleteCookie = useCallback(() => {
Cookies.remove(name)
setValue('') // або null, залежить від вашої логіки
}, [name])
return [value, updateCookie, deleteCookie] as const
}
Використання в компоненті:
import React from 'react'
import useCookie from '../hooks/useCookie'
const ThemeToggler = () => {
const [theme, setTheme, removeTheme] = useCookie('theme', 'light')
return (
<div>
<p>Current Theme: {theme}</p>
<button onClick={() => setTheme('light')}>Light</button>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={removeTheme}>Reset</button>
</div>
)
}
useCookieВ Vue 3 Composition API це виглядає ще елегантніше завдяки ref та watch.
import { ref, watch } from 'vue'
import Cookies from 'js-cookie'
export function useCookie(name: string, defaultValue: string = '') {
// Ініціалізація
const cookieValue = Cookies.get(name) || defaultValue
const cookieRef = ref(cookieValue)
// Слідкуємо за змінами змінної і пишемо в куки
watch(cookieRef, (newValue) => {
if (newValue === null || newValue === undefined) {
Cookies.remove(name)
} else {
Cookies.set(name, newValue)
}
})
return cookieRef
}
Використання:
<script setup lang="ts">
import { useCookie } from '../composables/useCookie'
const lang = useCookie('app_lang', 'en')
</script>
<template>
<select v-model="lang">
<option value="en">English</option>
<option value="uk">Ukrainian</option>
</select>
</template>
Як тестувати код, що залежить від js-cookie, у Jest або Vitest? Ми не хочемо, щоб тести залежали від реального браузерного середовища або забруднювали його.
Найпростіший спосіб — замокати всю бібліотеку.
import { AuthService } from '../auth'
import Cookies from 'js-cookie'
// Автоматичний мок всіх методів бібліотеки
vi.mock('js-cookie')
describe('AuthService', () => {
it('should save token to cookies on login', () => {
const token = 'fake-jwt-token'
AuthService.login(token)
// Перевіряємо, чи був викликаний метод set
expect(Cookies.set).toHaveBeenCalledWith('auth_token', token, expect.any(Object))
})
it('should read token from cookies', () => {
// Налаштовуємо мок на повернення значення
Cookies.get.mockReturnValue('stored-token')
const token = AuthService.getToken()
expect(token).toBe('stored-token')
})
})
Якщо ви використовуєте jsdom (дефолт для Vitest/Jest для фронтенду), document.cookie там працює! js-cookie буде працювати "як справжній".
Але тут є ризик: тести можуть впливати один на одного (shared state). Треба чистити куки після кожного тесту.
afterEach(() => {
// Очистка всіх кук
Object.keys(Cookies.get()).forEach((cookieName) => {
Cookies.remove(cookieName)
})
})
Якщо ви використовуєте Next.js, Nuxt або Remix, ви зіткнетесь із проблемою: js-cookie працює з document, якого не існує на сервері.
Якщо ви спробуєте імпортувати і використати js-cookie в коді, який виконується під час серверного рендерингу (наприклад, у верхній частині компонента), ви отримаєте помилку.
Правильне використання в SSR:
useEffect (React) або onMounted (Vue) гарантовано запускаються тільки в браузері.const isBrowser = typeof window !== 'undefined'
if (isBrowser) {
Cookies.set('foo', 'bar')
}
Якщо ви рендерите HTML на сервері на основі куки (наприклад, "Темна тема"), а на клієнті js-cookie ще не встиг прочитати куку, ви отримаєте миготіння (Flash of Incorrect Content) або помилку гідратації.
Рішення для Next.js: Використовуйте спеціалізовані бібліотеки (nookies або cookies-next), які вміють читати куки з Server Request Headers і передавати їх у пропси.
Для суто клієнтських задач (наприклад, збереження стану закритого банера) js-cookie в useEffect — ідеальний варіант.
Маркетологи часто просять провести A/B тест. Покажемо 50% користувачів червону кнопку, а 50% — зелену, і збережемо вибір, щоб він не змінювався при оновленні сторінки.
import Cookies from 'js-cookie'
const TEST_NAME = 'ab_button_color'
const VARIANTS = ['red', 'green']
export function getVariant() {
// 1. Пробуємо отримати вже призначений варіант
let variant = Cookies.get(TEST_NAME)
// 2. Якщо вже є і він валідний — повертаємо
if (variant && VARIANTS.includes(variant)) {
return variant
}
// 3. Якщо немає — жеребкування (50/50)
variant = Math.random() < 0.5 ? 'red' : 'green'
// 4. Зберігаємо результат "навічно" (наприклад, 30 днів)
// Важливо: використовуємо SameSite Lax, щоб не губити тест при зовнішніх переходах
Cookies.set(TEST_NAME, variant, { expires: 30, sameSite: 'Lax', path: '/' })
// 5. Логуємо подію аналітики (псевдокод)
console.log(`[Analytics] Assigned A/B variant: ${variant}`)
return variant
}
Використання:
/* main.js */
import { getVariant } from './utils/ab-test'
const color = getVariant()
document.querySelector('.buy-button').style.backgroundColor = color
Цей код гарантує Consistency (постійність). Користувач не побачить, як кнопка змінить колір при наступному візиті.
Згідно RFC, мінімально гарантований розмір — 4096 байт (4KB). Це включає назву, значення та атрибути. Якщо ви спробуєте записати більше, кука просто не збережеться (тихо відпаде).
Ліміти різняться, але "безпечна" цифра — 20 кук на домен. Chrome дозволяє до 180, але не варто на це покладатися.
Нативно — ні, це просто рядок. Але з js-cookie ви можете передати масив ['a', 'b'], і він автоматично конвертується в JSON-рядок.
Cookies.set('ids', [1, 2, 3])
const ids = Cookies.getJSON('ids') // [1, 2, 3]
Chrome має суворі політики щодо Secure cookies. Якщо ви тестуєте на http://localhost і ставите secure: true, кука може не зберегтися.
У нових версіях localhost вважається "безпечним контекстом", тому це мало б працювати, але якщо виникають проблеми — спробуйте прибрати secure для дев-режиму.
Типові ситуації, коли щось йде не так.
Secure куку на http:// (не localhost).localhost.localStorage або IndexedDB.path або domain.remove() має точно їх дублювати.js-cookie (наприклад, сервер PHP/Node), і формат не відповідає очікуваному (зайві символи, інше кодування).try/catch.| Критерій | Native document.cookie | js-cookie |
|---|---|---|
| Читання | Складний парсинг рядка, Regex | Cookies.get('name') |
| Запис | Ручне формування рядка | Cookies.set('name', 'val') |
| Кодування | Ручне encodeURIComponent | Автоматичне |
| Типи даних | Тільки String | Підтримка JSON (через конвертери) |
| Видалення | Встановлення дати в минуле (хак) | Cookies.remove('name') |
| Розмір коду | Багато бойлерплейту | 1 рядок |
js-cookie — це той випадок, коли маленька бібліотека економить години налагодження і кілометри нервів.
Щоб говорити однією мовою з колегами, запам'ятайте ці визначення:
Expires або Max-Age.Expires). Цінна для "Remember Me".document.cookie), що захищає її від крадіжки через XSS..google.com, буде доступна і для mail.google.com./admin).Закріпіть отримані знання про js-cookie:
Cookies, document.cookie та світ після "Cookiepocalypse"
Енциклопедія про HTTP Cookies. Від `document.cookie` до атрибутів безпеки (SameSite, Secure), боротьби з CSRF/XSS та майбутнього без сторонніх cookies.
Axios: Потужний HTTP-клієнт для JavaScript
Уявіть ситуацію: ви розробляєте сучасний веб-додаток, який має спілкуватися з десятками різних API — отримувати дані про продукти, авторизувати користувачів, завантажувати файли, обробляти помилки мережі. Ви могли б використовувати вбудований Fetch API, але швидко зрозуміли б, що для кожного запиту доводиться писати однотипний код: перевірка статусу, перетворення JSON, обробка таймаутів... Чи є спосіб працювати з HTTP-запитами елегантніше?