What you need
Next.js gives you fast pages and control over rendering, but it does not ship a comment system. You can build your own with a database and an API route, or you can drop in a hosted embed and skip all of that. This guide covers the embed route with Gabden, which works the same whether you use the App Router or the older Pages Router.
A Gabden thread is keyed to the page's canonical URL. That means each blog post gets its own discussion, and the thread stays attached to the URL even if you change your theme or rebuild the site.
The embed
The base snippet is one container and one script:
<div id='gabden-conversations'></div>
<script async src='https://YOUR-SITE.gabden.com/conversations.js'
data-page='canonical'
data-theme='auto'></script>
Copy your own Gabden subdomain (the YOUR-SITE part of the URL) from the dashboard after you create a site. In React you do not paste raw script tags into JSX, so the two routers each have a small idiomatic way to load it.
App Router
Create a client component that mounts the container and injects the script once. Mark it with 'use client' because it touches the DOM after the page renders.
'use client';
import { useEffect, useRef } from 'react';
export default function Comments() {
const ref = useRef(null);
useEffect(() => {
const s = document.createElement('script');
s.src = 'https://YOUR-SITE.gabden.com/conversations.js';
s.async = true;
s.setAttribute('data-page', 'canonical');
s.setAttribute('data-theme', 'auto');
ref.current.appendChild(s);
}, []);
return <div><div id='gabden-conversations'></div><div ref={ref} /></div>;
}
Import that component into your post page (for example app/blog/[slug]/page.jsx) and render it below the article body. Because the script reads the current page URL, you do not pass the slug in yourself.
Pages Router
The same component works in the Pages Router. You can also use the built-in next/script component with the lazyOnload strategy so the embed loads after the main content, which keeps your Largest Contentful Paint clean:
import Script from 'next/script';
export default function Comments() {
return (
<>
<div id='gabden-conversations'></div>
<Script src='https://YOUR-SITE.gabden.com/conversations.js'
data-page='canonical' data-theme='auto' strategy='lazyOnload' />
</>
);
}
Canonical URLs and routing
Next.js apps often serve the same content under more than one path (trailing slash, query strings, a preview domain). Since the thread is keyed to the canonical URL, set a real canonical tag in your metadata so every visitor lands on the same thread. In the App Router you do this with the metadata export and its alternates.canonical field. If you skip this, a post reached with and without a trailing slash could show two separate threads.
Client-side navigation is the other thing to watch. When a reader moves between posts without a full page reload, make sure the comment component is part of the page that unmounts and remounts, so the embed re-reads the new URL. Keying the component on the route slug is enough.
Performance
The embed is about 10KB and loads on its own, so it does not block your React bundle or your first render. Comments appear after the article, which is where readers expect them. There are no tracking cookies and no fingerprinting, so you are not adding a privacy liability to an otherwise lean site.
Moderation and identity
Readers can post anonymously, as a guest with a name and email, or by signing in with Google or GitHub. You choose how strict to be: pre-moderate everything, auto-approve, auto-approve returning verified people, or moderate only anonymous users. For a developer blog, GitHub sign-in plus auto-approving returning people tends to keep the queue small.
The full reference, including per-page rules to turn comments off on specific URLs, is in the docs. If you want to see the reader-facing thread first, look at Conversations.




Join the discussion