This article provides step-by-step instructions for installing Termly's Consent Management Platform (CMP) in Next.js 15 or 16 applications.
Prerequisites
- Next.js version 15.5.6 or 16.1.6 (versions above these are not supported yet)
- A Termly account with a configured website.
- Your Termly website UUID from your dashboard.
Step 1: Create the Termly Component
Create a new file at `src/components/TermlyCMP.tsx`:
'use client'
import { useEffect, useMemo, useRef } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
declare global {
interface Window {
Termly?: {
initialize: () => void
}
}
}
const SCRIPT_SRC_BASE = 'https://app.termly.io'
interface TermlyCMPProps {
websiteUUID: string
autoBlock?: boolean
masterConsentsOrigin?: string
}
export default function TermlyCMP({ autoBlock, masterConsentsOrigin, websiteUUID }: TermlyCMPProps) {
const scriptSrc = useMemo(() => {
const src = new URL(SCRIPT_SRC_BASE)
src.pathname = `/resource-blocker/${websiteUUID}`
if (autoBlock) {
src.searchParams.set('autoBlock', 'on')
}
if (masterConsentsOrigin) {
src.searchParams.set('masterConsentsOrigin', masterConsentsOrigin)
}
return src.toString()
}, [autoBlock, masterConsentsOrigin, websiteUUID])
const isScriptAdded = useRef(false)
const scriptRef = useRef<HTMLScriptElement | null>(null)
useEffect(() => {
if (isScriptAdded.current || typeof window === 'undefined') return
// Check if script already exists
const existingScript = document.querySelector(`script[src="${scriptSrc}"]`)
if (existingScript) {
isScriptAdded.current = true
return
}
const script = document.createElement('script')
script.src = scriptSrc
script.async = true
scriptRef.current = script
document.head.appendChild(script)
isScriptAdded.current = true
return () => {
// Cleanup function to remove script if component unmounts
if (scriptRef.current && scriptRef.current.parentNode) {
try {
scriptRef.current.parentNode.removeChild(scriptRef.current)
} catch (e) {
console.warn('Failed to remove Termly script:', e)
}
}
}
}, [scriptSrc])
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
if (typeof window !== 'undefined' && window.Termly) {
try {
window.Termly.initialize()
} catch (e) {
console.warn('Error initializing Termly:', e)
}
}
}, [pathname, searchParams])
return null
}
This
TermlyCMP component dynamically loads the Termly consent script and reinitializes it on route changes.
Step 2: Use the Termly Component Based on Your Router Type
Next.js provides two router architectures:
pages router and app router. Follow the relevant steps based on which one your project uses.Note: Replace `YOUR_WEBSITE_UUID` with your actual Termly website UUID, as listed in your dashboard.
App Router Implementation:
If your project uses the app router, add the Termly CMP component in your app layout `src/app/layout.tsx` file:
import "./globals.css";
import TermlyCMP from "./component/TermlyCMP";
import { Suspense } from "react";
const WEBSITE_UUID = "Your website UUID";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body suppressHydrationWarning={true}>
{children}
<Suspense fallback={null}>
<TermlyCMP
websiteUUID={WEBSITE_UUID}
autoBlock={true}
/>
</Suspense>
</body>
</html>
);
}This wraps your app with the Termly component inside a Suspense boundary (required for App Router).
Pages Router Implementation:
If your project uses the pages router, add the Termly CMP component in your app layout `pages/_app.tsx`:
import type { AppProps } from 'next/app'
import TermlyCMP from '../components/TermlyCMP'
import '../styles/globals.css'
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<TermlyCMP
websiteUUID="YOUR_WEBSITE_UUID"
autoBlock={true}
/>
</>
)
}In this setup, the
TermlyCMP component is added to the layout to ensure the consent banner is available across all routes. This wraps all pages with the Termly component (no Suspense needed in Pages Router).