npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fredonbytes-ackee-consent

v1.0.0

Published

Lightweight, themeable Ackee analytics integration with cookie consent banner for Next.js App Router

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-consent

Quick 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-id

2. 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

  1. Create an NPM account at npmjs.com

  2. Login to NPM from your terminal:

    npm login
  3. Verify you're logged in:

    npm whoami

Publishing a New Version

  1. Navigate to the package directory:

    cd packages/fredonbytes-ackee-consent
  2. Ensure the build is up to date:

    npm run build
  3. Verify package contents (dry run):

    npm pack --dry-run
  4. Publish to NPM:

    npm publish

    For 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 --tags

Pre-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 beta

Users can install pre-release versions with:

npm install fredonbytes-ackee-consent@beta

Complete 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-consent

Step 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 | fullscreen

Step 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

  1. Check provider wrapping: Ensure <AckeeProvider> wraps your entire app
  2. Clear localStorage: Run localStorage.removeItem('fredonbytes-consent-status') in browser console
  3. Check for SSR issues: The banner requires client-side hydration

Tracking Not Working

  1. Verify environment variables: Check NEXT_PUBLIC_ACKEE_SERVER and NEXT_PUBLIC_ACKEE_DOMAIN_ID
  2. Check Ackee server: Ensure your Ackee instance is running and accessible
  3. Check browser console: Look for [fredonbytes-ackee-consent] warnings

Styling Issues

  1. CSS variables not applying: Ensure variables are defined in :root or appropriate scope
  2. Dark mode not working: Verify your dark mode class (.dark) matches your theme setup

License

MIT © FredonBytes