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

@cookieyes/nextjs

v0.6.3

Published

Next.js App Router adapter for the CookieYes consent SDK

Readme


Free-tier note: the "Powered by CookieYes" attribution in the banner may not be removed on the free tier. Paid plans remove it.


Key features

  • App Router & Pages Router — one component works in both.
  • SSR-safe — the banner is server-rendered into the first paint with no hydration mismatch.
  • GDPR & CCPA — opt-in and "Do Not Sell" opt-out flows.
  • 1:1 with @cookieyes/react — every component, hook, and primitive, re-exported.

Prerequisites

  • Node.js ≥ 20
  • Next.js ≥ 14 (App Router or Pages Router)
  • React ≥ 18 and React DOM ≥ 18 (peer dependencies)

| Next.js | React | React DOM | Verified | Build + typecheck | SSR (HTML + cookie value) | Behaviour (jsdom) | |---|---|---|---|---|---|---| | 14.0.0 (declared floor) | 18.0.0 (declared floor) | 18.0.0 | 2026-08-27 | pass | pass | pass | | 15.5.24 | 18.3.1 | 18.3.1 | 2026-08-27 | pass | pass | pass | | 16.3.0 | 19.2.8 | 19.2.8 | 2026-08-27 | pass | pass | pass |

Verified by installing the exact versions above, building a real Next.js app, server-rendering it and asserting the banner's HTML (and a returning visitor's cookie value) is correct, then running behaviour assertions in jsdom via @cookieyes/test. Node 20, pnpm. See rationale for why this stops short of a real browser.

Not verified by this table:

  • Any Next.js/React version outside the rows above.
  • npm/yarn as the install client.
  • No real browser paint or hydration — SSR is asserted via server-rendered HTML/fetch only; client behaviour is asserted in jsdom, not a browser DOM (no Playwright).
  • Node, package-manager and module-resolution dimensions are not yet varied — every combination above ran on Node 20 / pnpm / moduleResolution=bundler.

Quick start

1. Install the package

npm install @cookieyes/nextjs
pnpm add @cookieyes/nextjs
yarn add @cookieyes/nextjs
bun add @cookieyes/nextjs

2. Create a client consent-manager component

Because initCookieYes() and the components are client-side, this file must start with "use client".

Which API should I read consent with? This package re-exports @cookieyes/react verbatim, so the same guidance applies: useConsent() in client components, and for Server Components or route handlers (no React hooks available), read the raw cookie with parseCookie from @cookieyes/core. See the shared decision tree and @cookieyes/react's Hooks section for the full low-level surface.

// components/consent-manager.tsx
"use client";

import {
  CookieBanner,
  CookiePreferences,
  RecallButton,
  initCookieYes,
} from "@cookieyes/nextjs";
import "@cookieyes/nextjs/styles.css";

initCookieYes({
  mode: "cookie-only",    // "cookie-only" | "self-hosted"
  regulation: "GDPR",     // "GDPR" | "CCPA"
  colorScheme: "system",  // "light" | "dark" | "system"
});

export function CookieYesRoot() {
  return (
    <>
      <CookieBanner />
      <CookiePreferences />
      <RecallButton />
    </>
  );
}

The @cookieyes/nextjs/styles.css import is required — the components ship no inline styling, so without it they render unstyled.

The sheet itself lives in @cookieyes/react and is re-exported here as a one-line @import, so there is still only one copy of it. Import it from this package, not from @cookieyes/react: under pnpm's strict node_modules layout an app that installed only @cookieyes/nextjs cannot resolve @cookieyes/react, and the import fails with MODULE_NOT_FOUND. npm and Yarn Classic hoist it and resolve either path; @cookieyes/nextjs/styles.css is correct under all of them. @cookieyes/nextjs/critical.css is re-exported the same way.

3. Mount it in your root layout — the layout itself stays a Server Component:

// app/layout.tsx
import { CookieYesRoot } from "@/components/consent-manager";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <CookieYesRoot />
        {children}
      </body>
    </html>
  );
}

4. Done. The banner appears on first load. If it doesn't, see Troubleshooting.

Prefer zero manual setup? Run npx @cookieyes/cli init — it detects Next.js (App or Pages Router) and wires this up for you.

Pages Router

The same CookieYesRoot component works in the Pages Router — render it in pages/_app.tsx:

import type { AppProps } from "next/app";
import { CookieYesRoot } from "@/components/consent-manager";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <CookieYesRoot />
    </>
  );
}

CCPA

For regulation: "CCPA", also render the opt-out dialog:

import { CookieBanner, CookiePreferences, CookieOptOut, RecallButton } from "@cookieyes/nextjs";

// inside CookieYesRoot:
<>
  <CookieBanner />
  <CookiePreferences />
  <CookieOptOut />
  <RecallButton />
</>

Region-based regulation (server-detected)

Pick the banner's regulation from the visitor's region. On the server you read the location header your host adds (Cloudflare/Vercel) with regionFromHeaders, pass it to your client component, and wrap the banner in <CookieYesProvider> — so the correct banner is server-rendered for each visitor, on the first paint, with no post-hydration flicker.

// app/layout.tsx — a Server Component
import { headers } from "next/headers";
import { regionFromHeaders } from "@cookieyes/nextjs";
import { CookieYesRoot } from "./cookieyes-root"; // your "use client" module

export default async function RootLayout({ children }) {
  const region = regionFromHeaders(await headers()); // "US-CA" | "DE" | undefined
  return (
    <html>
      <body>
        <CookieYesRoot region={region} />
        {children}
      </body>
    </html>
  );
}
// cookieyes-root.tsx — "use client"
"use client";
import { initCookieYes, CookieYesProvider, CookieBanner, CookieOptOut } from "@cookieyes/nextjs";

const map = { "US-CA": "CCPA", DE: "GDPR" } as const;

export function CookieYesRoot({ region }: { region?: string }) {
  const regionConfig = { detect: () => region, map };
  initCookieYes({ mode: "cookie-only", region: regionConfig });
  return (
    <CookieYesProvider region={regionConfig}>
      <CookieBanner />
      <CookieOptOut /> {/* render this too if any region maps to CCPA */}
    </CookieYesProvider>
  );
}
  • What it reads: by default the well-known Vercel (x-vercel-ip-country + -region) and Cloudflare (cf-ipcountry) headers. Pass regionFromHeaders(h, { header: "x-your-header" }) to read a custom one.
  • headers() is awaited on Next.js 15+ and synchronous on 14 — use whichever your version needs.
  • First paint: with the provider, the server resolves the region per request and renders the right banner directly into the HTML — a US visitor gets CCPA, an EU visitor gets GDPR, on the first byte. The provider resolves the same value on the client, so there's no hydration mismatch. (Without the provider, the banner still works but is corrected after hydration rather than server-rendered per request.)
  • GPC: on a CCPA banner, the browser's "do not sell" signal (navigator.globalPrivacyControl) starts the visitor opted out — non-required categories denied, so gated scripts/iframes never load — until they choose otherwise. It's read in the browser (the server can't see it), so it applies right after hydration; it never changes which banner shows. Set region.honorGpc: false to ignore it.

Returning visitors — no banner flash

By default the server doesn't know whether a visitor has already chosen, so it renders the banner for everyone and the client removes it after hydration. A returning visitor sees the banner appear and then vanish, which reads as a bug rather than as a remembered choice.

Read their decision from the request and pass it to the provider, and the banner is never in their HTML at all — nothing to hide, so nothing flashes:

// app/layout.tsx — a Server Component
import { CookieYesProvider } from "@cookieyes/nextjs";
import { getServerConsent } from "@cookieyes/nextjs/server";
import { CookieYesRoot } from "./cookieyes-root";

export default async function RootLayout({ children }) {
  const initialConsent = await getServerConsent({ regulation: "GDPR" });
  return (
    <html lang="en">
      <body>
        <CookieYesProvider regulation="GDPR" initialConsent={initialConsent}>
          <CookieYesRoot />
        </CookieYesProvider>
        {children}
      </body>
    </html>
  );
}
  • Import from @cookieyes/nextjs/server, not the main entry. It reads next/headers and is server-only; the main entry is "use client".
  • Returns null when the banner should show — a first-time visitor, a cookie recording no choice yet, a corrupt cookie, or one written against a different category taxonomy (which the client re-requests too). Passing null renders exactly as before, so this is safe to add everywhere.
  • initialConsent is a provider prop, never an initCookieYes option. The consent runtime is a module-level singleton shared across concurrent requests, so per-visitor state there would leak between visitors — the same reason region/regulation go through the provider.
  • Combine it with region from the section above; both are per-request and both belong on the provider.
  • getServerConsent() calls cookies(), which opts the route into dynamic rendering, as any cookies() call does. On a statically rendered route there's no request to read, so the banner is server-rendered for everyone and hidden on the client as before.
  • Framework-agnostic alternative: readServerConsent(cookieHeader, options) from @cookieyes/core takes the raw Cookie header, for Pages Router getServerSideProps, middleware, or any other SSR setup.

Google Consent Mode (GA4, Ads, GTM)

Google tags need a Consent Mode deny-by-default set before any tag runs and before the SDK boots — so a returning visitor's saved choice applies from first paint. Render <GoogleConsentMode /> high in your root layout:

// app/layout.tsx
import { CookieYesProvider } from "@cookieyes/nextjs";
import { GoogleConsentMode } from "@cookieyes/nextjs/server";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <GoogleConsentMode />
        <CookieYesProvider regulation="GDPR">{children}</CookieYesProvider>
      </body>
    </html>
  );
}

Then load the tags on the client with a preset from @cookieyes/scripts — ga4(), googleAds(), or googleTagManager(). The SDK broadcasts each consent change to Google as a Consent Mode update; you don't wire that up.

Faster first paint (critical CSS)

The banner is server-rendered, so it is in the very first HTML — but the browser cannot paint it until the stylesheet arrives, and that is a second round trip. On a slow connection the round trip costs more than the whole SDK: on the Lighthouse Mobile profile, first paint is 468 ms with the stylesheet as a <link> and 224 ms with the banner's rules inlined. Nil difference on fast desktop.

Two lines, and it is opt-in — nothing changes unless you add them:

// app/layout.tsx
import { CookieYesStyles } from "@cookieyes/nextjs/server";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <CookieYesStyles />
      </head>
      <body>{children}</body>
    </html>
  );
}
// app/cookieyes/styles.css/route.ts — the SDK serves its own stylesheet
export { GET } from "@cookieyes/nextjs/styles-route";

Then remove import "@cookieyes/nextjs/styles.css" — leaving it puts the sheet back on the critical path and cancels the gain.

<CookieYesStyles /> inlines critical.css (only the banner's rules) in <head> and loads the full sheet with media="print", which the browser fetches without blocking render; the SDK switches it on once mounted. There is no inline script.

If you run a strict Content-Security-Policy, the inlined block needs one hash on style-src — not 'unsafe-inline':

import { CRITICAL_CSS_HASH } from "@cookieyes/nextjs/server"; // "'sha256-…'"

It changes only when the stylesheet does, and is published in the changelog. Nonce users can pass <CookieYesStyles nonce={nonce} /> instead. Without a CSP there is nothing further to configure.

API

This package re-exports the entire @cookieyes/react surface — the setup function (initCookieYes), components (CookieBanner, CookiePreferences, CookieOptOut, RecallButton, GatedScript, GatedFrame), headless primitives (Banner, Preferences, OptOut), and all hooks (useConsent, useConsentActions, …).

It also adds server-only exports on their own subpath, kept out of the "use client" barrel:

| Import | Export | Purpose | |---|---|---| | @cookieyes/nextjs/server | getServerConsent(options?) | Reads the request's cookies and returns a returning visitor's stored decision (or null), for <CookieYesProvider initialConsent> | | @cookieyes/nextjs/server | <GoogleConsentMode /> | Renders the Google Consent Mode deny-by-default into the page <head> (see below) |

Troubleshooting

The banner doesn't appear. Ensure <CookieYesRoot /> is mounted in your root layout.tsx (App Router) or _app.tsx (Pages Router), and that initCookieYes(...) runs in the "use client" consent-manager module. The banner only shows while the user hasn't acted — clear the cookieyes-consent cookie and reload while testing.

I get a "use client" error. The consent-manager file (the one calling initCookieYes and importing the components) must start with "use client". Keep your layout.tsx a Server Component and import <CookieYesRoot /> into it — don't add "use client" to the layout itself.

Hydration mismatch on load. Use @cookieyes/nextjs (not @cookieyes/react) so the banner is pre-marked "use client". The server-rendered banner carries your configured regulation, so keep that value stable — rendering with a different regulation on the client than on the server causes a mismatch.

Still stuck? Open an issue.

Community & support

(A community chat channel is on the roadmap.)

Contributing

Contributions are welcome. Read our Contributing Guidelines and Code of Conduct, then open a pull request.

Security

Found a vulnerability? Do not open a public issue — follow our Security Policy and use GitHub's private vulnerability reporting.

License

MIT — see LICENSE. The "Powered by CookieYes" attribution may not be removed on the free tier.