fredonbytes-ackee-consent
v1.0.0
Published
Lightweight, themeable Ackee analytics integration with cookie consent banner for Next.js App Router
Maintainers
Readme
fredonbytes-ackee-consent
A lightweight, themeable NPM package for integrating Ackee analytics with a customizable cookie consent banner in Next.js App Router projects.
Privacy-first by design: Anonymous tracking by default, detailed tracking only with explicit user consent.
Features
- 🔒 Privacy-First: Anonymous tracking by default, detailed only with consent
- 🎨 Themeable: CSS variable-based theming for easy customization
- 📱 Three Banner Modes: Bottom bar, top bar, or fullscreen modal
- 🌐 i18n Ready: All text content customizable via props for next-intl compatibility
- ⚡ Lightweight: Minimal bundle size with lazy-loaded Ackee tracker
- 🔄 SPA Support: Automatic route change tracking for Next.js App Router
- 💾 Persistent Consent: User preference saved to localStorage
Installation
npm install fredonbytes-ackee-consent
# or
pnpm add fredonbytes-ackee-consent
# or
yarn add fredonbytes-ackee-consentQuick Start
1. Set Environment Variables
Create or update your .env.local:
NEXT_PUBLIC_ACKEE_SERVER=https://your-ackee-instance.com
NEXT_PUBLIC_ACKEE_DOMAIN_ID=your-domain-id2. Wrap Your App
In your root layout (app/layout.tsx):
import { AckeeProvider, CookieConsentBanner } from 'fredonbytes-ackee-consent';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AckeeProvider>
{children}
<CookieConsentBanner />
</AckeeProvider>
</body>
</html>
);
}That's it! The banner will appear for new visitors, and Ackee tracking will work based on their consent choice.
Configuration
Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| NEXT_PUBLIC_ACKEE_SERVER | Yes | - | URL of your Ackee server |
| NEXT_PUBLIC_ACKEE_DOMAIN_ID | Yes | - | Domain ID from Ackee dashboard |
| NEXT_PUBLIC_CONSENT_BANNER_MODE | No | bottom | Banner mode: bottom, top, or fullscreen |
Provider Configuration
You can also pass configuration directly to the provider:
<AckeeProvider
config={{
server: 'https://ackee.example.com',
domainId: 'your-domain-id'
}}
enabled={true} // Set to false to disable tracking entirely
>
{children}
</AckeeProvider>Banner Modes
Bottom Banner (Default)
<CookieConsentBanner mode="bottom" />A non-intrusive banner fixed to the bottom of the viewport.
Top Banner
<CookieConsentBanner mode="top" />A banner fixed to the top of the viewport.
Fullscreen Modal
<CookieConsentBanner mode="fullscreen" />A modal overlay that blurs the background, requiring user action before proceeding.
Theming
Customize the banner appearance by setting CSS variables in your globals.css:
:root {
/* Banner container */
--consent-bg: #ffffff;
--consent-text: #1f2937;
--consent-border: #e5e7eb;
--consent-shadow: 0 -4px 6px -1px rgb(0 0 0 / 0.1);
--consent-radius: 0.5rem;
/* Accept button */
--consent-accept-bg: #059669;
--consent-accept-text: #ffffff;
--consent-accept-hover-bg: #047857;
/* Decline button */
--consent-decline-bg: transparent;
--consent-decline-text: #6b7280;
--consent-decline-hover-bg: #f3f4f6;
/* Fullscreen overlay */
--consent-overlay-bg: rgb(0 0 0 / 0.5);
--consent-modal-radius: 1rem;
}
/* Dark mode example */
.dark {
--consent-bg: #1f2937;
--consent-text: #f9fafb;
--consent-border: #374151;
--consent-accept-bg: #10b981;
--consent-decline-text: #9ca3af;
--consent-decline-hover-bg: #374151;
}Internationalization (i18n)
All text content can be customized via props, making it easy to integrate with next-intl:
// Using next-intl
import { useTranslations } from 'next-intl';
export default function Layout({ children }) {
const t = useTranslations('consent');
return (
<AckeeProvider>
{children}
<CookieConsentBanner
title={t('title')}
description={t('description')}
acceptLabel={t('accept')}
declineLabel={t('decline')}
privacyPolicyUrl="/privacy"
privacyPolicyLabel={t('privacyPolicy')}
/>
</AckeeProvider>
);
}CookieConsentBanner Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| mode | 'bottom' \| 'top' \| 'fullscreen' | 'bottom' | Banner display mode |
| className | string | - | Additional CSS classes |
| title | string | 'Cookie Preferences' | Banner title |
| description | string | (Default text) | Banner description |
| acceptLabel | string | 'Accept Analytics' | Accept button text |
| declineLabel | string | 'Decline' | Decline button text |
| privacyPolicyUrl | string | - | Link to privacy policy |
| privacyPolicyLabel | string | 'Privacy Policy' | Privacy link text |
| onAccept | () => void | - | Callback when user accepts |
| onDecline | () => void | - | Callback when user declines |
Privacy Policy Helper
Include a pre-built privacy policy section explaining your analytics:
import { PrivacyPolicyHelper } from 'fredonbytes-ackee-consent';
export default function PrivacyPage() {
return (
<div>
<h1>Privacy Policy</h1>
<PrivacyPolicyHelper
showResetButton={true}
onReset={() => console.log('User reset preferences')}
/>
</div>
);
}The helper component is fully customizable with i18n props for all text content.
Hooks
useConsent
Access consent state anywhere in your app:
import { useConsent } from 'fredonbytes-ackee-consent';
function MyComponent() {
const { status, hasDecided, accept, decline, reset } = useConsent();
// status: 'pending' | 'accepted' | 'declined'
// hasDecided: boolean
// accept, decline, reset: () => void
return (
<div>
Current status: {status}
{hasDecided && (
<button onClick={reset}>Reset preferences</button>
)}
</div>
);
}useAckee
For advanced usage, directly access the Ackee tracking hook:
import { useAckee } from 'fredonbytes-ackee-consent';
function MyComponent() {
// Automatically tracks page views based on consent
useAckee({
server: 'https://ackee.example.com',
domainId: 'your-domain-id',
enabled: true,
});
return <div>...</div>;
}Privacy Model
This package implements a privacy-first approach:
| Consent Status | Ackee detailed | Data Collected |
|----------------|------------------|----------------|
| No decision yet | false | Anonymous page views only |
| Declined | false | Anonymous page views only |
| Accepted | true | Full device/browser details |
Anonymous tracking (detailed: false):
- Page views
- Referrer (where user came from)
- General browser info
Detailed tracking (detailed: true):
- All anonymous data
- Device type and manufacturer
- Operating system
- Browser name and version
- Screen resolution
- Visit duration
TypeScript
Full TypeScript support with exported types:
import type {
ConsentStatus,
BannerMode,
AckeeConfig,
ConsentBannerProps,
ConsentContextValue,
} from 'fredonbytes-ackee-consent';Browser Support
- All modern browsers
- Next.js 14, 15, and 16
- React 18 and 19
For Package Maintainers
Publishing to NPM
First-Time Setup
Create an NPM account at npmjs.com
Login to NPM from your terminal:
npm loginVerify you're logged in:
npm whoami
Publishing a New Version
Navigate to the package directory:
cd packages/fredonbytes-ackee-consentEnsure the build is up to date:
npm run buildVerify package contents (dry run):
npm pack --dry-runPublish to NPM:
npm publishFor scoped packages (e.g.,
@fredonbytes/ackee-consent), use:npm publish --access public
Updating the Package
Version Bumping
Use semantic versioning (SemVer):
| Change Type | Command | Example |
|-------------|---------|---------|
| Bug fixes | npm version patch | 1.0.0 → 1.0.1 |
| New features (backward compatible) | npm version minor | 1.0.0 → 1.1.0 |
| Breaking changes | npm version major | 1.0.0 → 2.0.0 |
Full Update Workflow
# 1. Make your code changes
# 2. Update version (automatically creates git tag)
npm version patch # or minor/major
# 3. Build the package
npm run build
# 4. Publish to NPM
npm publish
# 5. Push changes and tags to git
git push && git push --tagsPre-release Versions
For beta or alpha releases:
# Beta release
npm version prerelease --preid=beta
# Results in: 1.0.1-beta.0
# Publish with tag
npm publish --tag betaUsers can install pre-release versions with:
npm install fredonbytes-ackee-consent@betaComplete Usage Guide for Consumer Projects
Step 1: Install the Package
# Using npm
npm install fredonbytes-ackee-consent
# Using pnpm
pnpm add fredonbytes-ackee-consent
# Using yarn
yarn add fredonbytes-ackee-consent
# Using bun
bun add fredonbytes-ackee-consentStep 2: Configure Environment Variables
Create or update .env.local in your project root:
# Required
NEXT_PUBLIC_ACKEE_SERVER=https://your-ackee-server.com
NEXT_PUBLIC_ACKEE_DOMAIN_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Optional
NEXT_PUBLIC_CONSENT_BANNER_MODE=bottom # bottom | top | fullscreenStep 3: Add to Your Layout
Update your root layout file (app/layout.tsx):
import { AckeeProvider, CookieConsentBanner } from 'fredonbytes-ackee-consent';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<AckeeProvider>
{children}
<CookieConsentBanner />
</AckeeProvider>
</body>
</html>
);
}Step 4: Customize Theming (Optional)
Add CSS variables to your app/globals.css:
:root {
/* Match your brand colors */
--consent-bg: #ffffff;
--consent-text: #171717;
--consent-border: #e5e5e5;
--consent-accept-bg: #22c55e;
--consent-accept-text: #ffffff;
--consent-accept-hover-bg: #16a34a;
--consent-decline-text: #737373;
}
/* Dark mode support */
.dark {
--consent-bg: #171717;
--consent-text: #fafafa;
--consent-border: #262626;
--consent-accept-bg: #22c55e;
--consent-decline-text: #a3a3a3;
--consent-decline-hover-bg: #262626;
}Step 5: Add Privacy Policy Page (Recommended)
Create app/privacy/page.tsx:
import { PrivacyPolicyHelper } from 'fredonbytes-ackee-consent';
export const metadata = {
title: 'Privacy Policy',
};
export default function PrivacyPage() {
return (
<main className="container mx-auto px-4 py-8 max-w-3xl">
<h1 className="text-3xl font-bold mb-8">Privacy Policy</h1>
{/* Your custom privacy content here */}
<section className="mb-8">
<h2 className="text-2xl font-semibold mb-4">Introduction</h2>
<p>Your privacy is important to us...</p>
</section>
{/* Analytics section with reset button */}
<PrivacyPolicyHelper
showResetButton={true}
onReset={() => {
// Optional: track reset event, show toast, etc.
}}
/>
</main>
);
}Complete Import Reference
// Components
import {
AckeeProvider, // Wrap your app with this
CookieConsentBanner, // The consent banner UI
PrivacyPolicyHelper, // Privacy page helper component
ConsentProvider, // Lower-level provider (advanced)
} from 'fredonbytes-ackee-consent';
// Hooks
import {
useConsent, // Access consent state
useAckee, // Direct Ackee tracking control
} from 'fredonbytes-ackee-consent';
// Types
import type {
ConsentStatus, // 'pending' | 'accepted' | 'declined'
BannerMode, // 'bottom' | 'top' | 'fullscreen'
AckeeConfig, // { server, domainId, detailed? }
ConsentBannerProps, // Props for CookieConsentBanner
ConsentContextValue, // { status, hasDecided, accept, decline, reset }
AckeeProviderProps, // Props for AckeeProvider
PrivacyPolicyHelperProps, // Props for PrivacyPolicyHelper
} from 'fredonbytes-ackee-consent';
// Constants (for advanced customization)
import {
DEFAULT_BANNER_CONTENT, // Default English text
DEFAULT_PRIVACY_CONTENT, // Default privacy page text
CSS_VARIABLES, // CSS variable names
CONSENT_STORAGE_KEY, // localStorage key
} from 'fredonbytes-ackee-consent';Advanced Usage Examples
Conditional Tracking Based on Consent
'use client';
import { useConsent } from 'fredonbytes-ackee-consent';
export function AnalyticsAwareButton() {
const { status } = useConsent();
const handleClick = () => {
if (status === 'accepted') {
// User has consented - track detailed event
console.log('Tracking detailed click event');
}
// Perform action regardless of consent
};
return <button onClick={handleClick}>Click me</button>;
}Custom Consent UI (Without Banner)
'use client';
import { ConsentProvider, useConsent } from 'fredonbytes-ackee-consent';
function CustomConsentUI() {
const { status, accept, decline, reset } = useConsent();
if (status !== 'pending') {
return (
<p>
You {status} analytics.
<button onClick={reset}>Change preference</button>
</p>
);
}
return (
<div className="my-custom-banner">
<p>We use analytics to improve our site.</p>
<button onClick={accept}>Allow</button>
<button onClick={decline}>Deny</button>
</div>
);
}
export function App({ children }) {
return (
<ConsentProvider>
<CustomConsentUI />
{children}
</ConsentProvider>
);
}With next-intl (Full i18n)
// app/[locale]/layout.tsx
import { useTranslations } from 'next-intl';
import { AckeeProvider, CookieConsentBanner } from 'fredonbytes-ackee-consent';
export default function LocaleLayout({ children, params: { locale } }) {
const t = useTranslations('cookies');
return (
<html lang={locale}>
<body>
<AckeeProvider>
{children}
<CookieConsentBanner
mode="bottom"
title={t('banner.title')}
description={t('banner.description')}
acceptLabel={t('banner.accept')}
declineLabel={t('banner.decline')}
privacyPolicyUrl={`/${locale}/privacy`}
privacyPolicyLabel={t('banner.privacyLink')}
/>
</AckeeProvider>
</body>
</html>
);
}Translation file (messages/en.json):
{
"cookies": {
"banner": {
"title": "Cookie Preferences",
"description": "We use privacy-friendly analytics to improve your experience.",
"accept": "Accept Analytics",
"decline": "Decline",
"privacyLink": "Privacy Policy"
}
}
}Troubleshooting
Banner Not Appearing
- Check provider wrapping: Ensure
<AckeeProvider>wraps your entire app - Clear localStorage: Run
localStorage.removeItem('fredonbytes-consent-status')in browser console - Check for SSR issues: The banner requires client-side hydration
Tracking Not Working
- Verify environment variables: Check
NEXT_PUBLIC_ACKEE_SERVERandNEXT_PUBLIC_ACKEE_DOMAIN_ID - Check Ackee server: Ensure your Ackee instance is running and accessible
- Check browser console: Look for
[fredonbytes-ackee-consent]warnings
Styling Issues
- CSS variables not applying: Ensure variables are defined in
:rootor appropriate scope - Dark mode not working: Verify your dark mode class (
.dark) matches your theme setup
License
MIT © FredonBytes
