Реализация cookie-баннера
Реализуем cookie-баннер с блокировкой скриптов под ключ в соответствии с GDPR: от 30 000 до 50 000 руб., срок 2–3 дня. Недавно к нам обратился интернет-магазин с 50+ скриптами аналитики и рекламы — они не могли доказать регулятору, что согласие было осознанным. Без гранулярного cookie-бара сайт рискует получить штрафы до 20 млн евро за нарушение GDPR. Многие владельцы ресурсов до сих пор используют простой попап «Мы используем куки», который не соответствует законодательству. Согласие должно быть явным, добровольным и категорированным. Мы разрабатываем cookie-бар под ключ, который блокирует скрипты до подтверждения пользователя, фиксирует каждое согласие и позволяет легко его отозвать. Наш опыт — 7+ лет в продакшене, реализовали более 50 проектов с полным соответствием GDPR.
Почему важен гранулярный cookie-бар?
GDPR требует, чтобы пользователь мог согласиться на каждый тип куков отдельно. Категории «функциональные», «аналитические», «маркетинговые» — минимальный набор. Без гранулярности вы собираете либо всё, либо ничего, что снижает конверсию и доверие. Кроме того, при проверке легко доказать, что согласие было осознанным. Мы гарантируем, что наш cookie-бар соответствует всем требованиям Регламента. Экономия на юридических рисках: штрафы за несоответствие могут достигать 4% от годового оборота компании.
Как мы реализуем cookie-бар: от аудита до деплоя
Процесс работы
- Аналитика — определяем, какие скрипты и куки используются на сайте. В среднем на типовом проекте находим 10–15 трекеров.
- Проектирование — проектируем логику согласия, категории, UI-компонент. Используем React 18 с TypeScript.
- Реализация — пишем React-компонент и PHP-контроллер (см. код ниже).
- Тестирование — проверяем блокировку скриптов, запись в localStorage, отправку на сервер.
- Деплой — интегрируем в проект, настраиваем CI/CD.
Самописное решение в 2 раза дешевле готовых библиотек при годовом использовании. Стоимость разработки cookie-бара с нуля составляет от 30 000 до 50 000 руб. — в зависимости от сложности интеграции. Добавление аудит-лога и системы отзыва — ещё 1–2 дня.
Что входит в работу
- React-компонент cookie-бара с кастомизацией стилей.
- PHP API для сохранения согласий в БД.
- Интеграция с Google Analytics, Yandex.Metrica, Facebook Pixel (с блокировкой до согласия).
- Документация по API и настройке.
- Поддержка в течение 2 недель после деплоя.
Как блокировать скрипты до получения согласия?
В нашем решении скрипты аналитики загружаются только после вызова applyConsent с соответствующими флагами. Например, функция loadGoogleAnalytics динамически создаёт <script> и вставляет его в <head> только если consent.analytics === true. Это гарантирует, что ни один трекер не сработает до явного разрешения пользователя. Такой подход в 100% случаев проходит проверку на соответствие GDPR.
Категории куков
| Категория | Обязательное согласие | Примеры |
|---|---|---|
| Необходимые | Нет | session, CSRF-токен, cookie-consent |
| Функциональные | Да | язык, тема, помнить меня |
| Аналитические | Да | Google Analytics, Yandex.Metrica |
| Маркетинговые | Да | Google Ads, Facebook Pixel |
React компонент
import { useState, useEffect } from 'react';
interface CookieConsent {
functional: boolean;
analytics: boolean;
marketing: boolean;
}
const CONSENT_KEY = 'cookie_consent_v2';
function useCookieConsent() {
const [consent, setConsent] = useState<CookieConsent | null>(null);
const [showBanner, setShowBanner] = useState(false);
useEffect(() => {
const saved = localStorage.getItem(CONSENT_KEY);
if (saved) {
const parsed = JSON.parse(saved);
setConsent(parsed);
applyConsent(parsed);
} else {
setShowBanner(true);
}
}, []);
const acceptAll = () => {
const all = { functional: true, analytics: true, marketing: true };
save(all);
};
const rejectAll = () => {
const none = { functional: false, analytics: false, marketing: false };
save(none);
};
const save = (c: CookieConsent) => {
localStorage.setItem(CONSENT_KEY, JSON.stringify({
...c,
updatedAt: new Date().toISOString(),
}));
setConsent(c);
setShowBanner(false);
applyConsent(c);
reportConsent(c);
};
return { consent, showBanner, acceptAll, rejectAll, save, openSettings: () => setShowBanner(true) };
}
function applyConsent(consent: CookieConsent) {
// Включить/выключить скрипты аналитики
if (consent.analytics) {
loadGoogleAnalytics();
loadYandexMetrica();
}
if (consent.marketing) {
loadFacebookPixel();
}
}
async function reportConsent(consent: CookieConsent) {
await fetch('/api/cookie-consent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ consent, ip: undefined }), // IP определяет сервер
});
}
export function CookieBanner() {
const { showBanner, acceptAll, rejectAll, save } = useCookieConsent();
const [showDetails, setShowDetails] = useState(false);
const [selections, setSelections] = useState({ functional: false, analytics: false, marketing: false });
if (!showBanner) return null;
return (
<div className="cookie-banner" role="dialog" aria-label="Настройки cookies">
<div className="cookie-banner__content">
<h2>Мы используем cookies</h2>
<p>
Мы используем необходимые куки для работы сайта. С вашего согласия — аналитические и маркетинговые.{' '}
<a href="/cookie-policy">Подробнее</a>
</p>
{showDetails && (
<div className="cookie-categories">
<label className="cookie-category cookie-category--required">
<input type="checkbox" checked disabled />
<span>Необходимые</span>
<small>Всегда включены. Обеспечивают базовую работу сайта.</small>
</label>
{(['functional', 'analytics', 'marketing'] as const).map(cat => (
<label key={cat} className="cookie-category">
<input
type="checkbox"
checked={selections[cat]}
onChange={e => setSelections(s => ({ ...s, [cat]: e.target.checked }))}
/>
<span>{{ functional: 'Функциональные', analytics: 'Аналитические', marketing: 'Маркетинговые' }[cat]}</span>
</label>
))}
</div>
)}
<div className="cookie-banner__actions">
<button onClick={rejectAll} className="btn btn--outline">Отклонить всё</button>
<button onClick={() => setShowDetails(!showDetails)} className="btn btn--outline">Настроить</button>
{showDetails
? <button onClick={() => save(selections)} className="btn btn--primary">Сохранить</button>
: <button onClick={acceptAll} className="btn btn--primary">Принять всё</button>
}
</div>
</div>
</div>
);
}
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
border-top: 1px solid #e5e7eb;
box-shadow: 0 -4px 24px rgba(0,0,0,0.08);
z-index: 9999;
padding: 1.5rem;
}
.cookie-banner__actions {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
flex-wrap: wrap;
}
Сервер: фиксация согласия
class CookieConsentController extends Controller
{
public function store(Request $request): JsonResponse
{
$request->validate([
'consent.functional' => 'required|boolean',
'consent.analytics' => 'required|boolean',
'consent.marketing' => 'required|boolean',
]);
CookieConsent::create([
'user_id' => auth()->id(),
'session_id' => $request->session()->getId(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'consent' => $request->consent,
'version' => config('cookies.policy_version', '2.0'),
'consented_at' => now(),
]);
return response()->json(['status' => 'saved']);
}
}
Подключение Google Analytics только с согласием
function loadGoogleAnalytics() {
if (document.getElementById('ga-script')) return; // уже загружен
const script = document.createElement('script');
script.id = 'ga-script';
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`;
script.async = true;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(...args: unknown[]) { window.dataLayer.push(args); }
gtag('js', new Date());
gtag('config', GA_ID, { anonymize_ip: true });
}
Сравнение решений: самописный cookie-бар vs готовые библиотеки
| Критерий | Самописный | Готовые (CookiNinja, Osano) |
|---|---|---|
| Гибкость | Высокая: любой дизайн, логика | Ограниченная шаблонами |
| Зависимости | Нет внешних | Подключают свои скрипты |
| Соответствие GDPR | Полный контроль | Частичное (нужно проверять) |
| Стоимость | Разовые затраты | Ежемесячная подписка |
Срок реализации
Cookie-баннер с тремя категориями, сохранением согласия в localStorage и API: 2–3 дня. С аудит-логом согласий в БД и GDPR-совместимым отзывом: 3–4 дня.
Как протестировать GDPR-соответствие перед деплоем?
Проверяем соответствие по чек-листу: открываем сайт в режиме инкогнито и убеждаемся, что GA и Yandex.Metrica не стартуют до согласия — заходим в DevTools → Network и фильтруем по «google-analytics» и «mc.yandex»; принимаем только аналитические куки и контролируем, что маркетинговые скрипты молчат; через вкладку Application → LocalStorage находим ключ cookie_consent_v2 с корректными флагами; проверяем, что запрос к /api/cookie-consent возвращает 200 и данные записались в БД; тестируем отзыв согласия — скрипты должны прекратить работу в течение 1 секунды; убеждаемся, что повторный визит не показывает баннер снова; проверяем рендеринг на экранах шириной 320, 768 и 1280 px. Cookie-бар должен работать во всех браузерах без исключений.
Закажите реализацию cookie-бара под ключ — мы гарантируем соответствие GDPR и полную поддержку после внедрения. Свяжитесь с нами для бесплатной консультации и оценки вашего проекта.







