Subscribe
May 23, 2023
6 min read
next.jsgithub

Add reactivity to your Next.js blog using giscus

Learn how to add A comments & reactions system to your Next.js blog powered by GitHub Discussions using giscus.

When you're in the process of blog development, it's really important to include a comment section that enables interaction with your visitors and encourages receiving feedback. This becomes even more crucial if you're a beginner in writing - just like me - as such feedback can significantly contribute to your progress.

When creating my blog-centric personal portfolio, I had a goal of launching it as soon as possible. However, considering including a comment feature, the implementation process could be time-consuming. That's when I started looking for a solution that was easy to set up yet provided essential commenting functionalities. It was during this search that I stumbled upon Giscus.

What is Giscus?

An open-source comments widget that harnesses the power of GitHub Discussions. giscus integrates seamlessly with GitHub repositories, making it convenient for developers who prefer using GitHub as a platform for managing their code and projects. In addition, it provides features such as thread-based discussions, Markdown support, reactions, and user authentication through GitHub accounts. Feel free to explore and experience it firsthand in the comment section below this post 👇.

Giscus drew significant inspiration from utterances, which utilize an issue-based comment system instead of discussions. I experimented with utterances initially, but I found it less convenient due to its reliance on an issue tracker for conversational purposes.

Opting for GitHub Discussions over issues offered numerous benefits:

  • Enables organized and separated discussions in a dedicated space.
  • Streamline conversations, promoting more transparent communication and reducing confusion compared to issue-based systems.
  • Come with built-in features tailored for collaborative discussions, enabling a richer and more interactive commenting experience for content creators and visitors.

As a result, Giscus offers numerous advantages compared to other services:

  • The visitors can react to a post.
  • The visitors can reply and react to a comment.
  • Have a dozen of built-in themes (including 'preferred_color_scheme', which can be very useful if you have a dark/light theme on your website) and supports custom themes.

How does it work?

When Giscus loads, it utilizes the GitHub Discussions Search API to find the corresponding Discussion associated with the current page, using various mappings like URL, pathname, title, etc.

For commenting, visitors need to authorize the Giscus app via the GitHub OAuth flow to post on their behalf. Alternatively, they can directly comment on the linked GitHub Discussion. Comment moderation can be managed on GitHub, giving you control over the comments.

When to use it?

  • Giscus is an ideal solution for static websites as it utilizes GitHub Discussions as the backend, eliminating the need to manage a separate backend. However, it can also be used for a backend website, making it adaptable for both scenarios.
  • If your project is open-source, Giscus will function exclusively on public repositories. However, you do have the option to create a separate repository solely for discussions.
  • giscus is particularly suitable for tech-related blogs where visitors are likelier to have a GitHub account.

Setting up your github account

By default, GitHub discussions are disabled. To enable them, navigate to your repository's Settings tab and ensure the Discussions box is checked under the Features section.

Enable GitHub Discussions

Once Discussions are enabled, installing the Giscus application is next. You can either click here to visit the app page or search for "giscus" on the GitHub Marketplace.

Since I've already installed it, I have the configure button; if you didn't install it before, you should see a green Install button; click on it as the process is straightforward.

Giscus app page in GitHub Marketplace

Choose the repository in which you wish to utilize GitHub Discussions. In my case, it's nextjs-giscus-blog.

Giscus app repository access in GitHub Marketplace

Giscus configuration

With our repository ready for use, the next step is configuring Giscus according to our requirements. To accomplish this, visit the official Giscus app and fill out the provided form.

  • Repository: Select the repository to which Giscus will connect, ensuring you provide the username and the repo name like this ayoubkhial/nextjs-giscus-blog.
  • Page ↔ Discussions Mapping: Select the mapping method that connects the embedding page with the embedded Discussion. This mapping can be accomplished through the pathname, URL, title, and more. I will opt for the pathname in our particular setup as it guarantees uniqueness.

  • Discussion Category: When GitHub Discussions are enabled, the repository will have default categories created. However, you can create your own categories according to your preferences.

  • Features: You have the freedom to choose which features to enable. In our setup, I will allow reactions for the main post and prefer to position the comment box above the comments. This will allow visitors to write a comment without scrolling through previous comments.

  • Theme: Select a theme that complements the design of your website. If your website already supports dark/light mode, leaving it as the "preferred color scheme" may be convenient.

Based on your preferences, the script tag will be automatically populated further down the Giscus configuration page. It will be similar to the following example. (Keep it close; we'll use it later in our Next.js app)

<script src="https://giscus.app/client.js"
    data-repo="ayoubkhial/nextjs-giscus-blog"
    data-repo-id="R_kgDOJlwoBA"
    data-category="Announcements"
    data-category-id="DIC_kwDOJlwoBM4CWpCu"
    data-mapping="pathname"
    data-strict="0"
    data-reactions-enabled="1"
    data-emit-metadata="1"
    data-input-position="top"
    data-theme="preferred_color_scheme"
    data-lang="en"
    data-loading="lazy"
    crossorigin="anonymous"
    async>
</script>
<script src="https://giscus.app/client.js"
    data-repo="ayoubkhial/nextjs-giscus-blog"
    data-repo-id="R_kgDOJlwoBA"
    data-category="Announcements"
    data-category-id="DIC_kwDOJlwoBM4CWpCu"
    data-mapping="pathname"
    data-strict="0"
    data-reactions-enabled="1"
    data-emit-metadata="1"
    data-input-position="top"
    data-theme="preferred_color_scheme"
    data-lang="en"
    data-loading="lazy"
    crossorigin="anonymous"
    async>
</script>

Setup Giscus in your next.js blog

Before creating the Comments component, let's add some environment variables that store some of the repository data. If you're using one repository for each environment, you may consider adding the following variables to your next.config.js file:

const nextConfig = {
	env: {
		COMMENTS_REPO: 'ayoubkhial/nextjs-giscus-blog',
		COMMENTS_REPO_ID: 'R_kgDOJlwoBA',
		COMMENTS_CATEGORY: 'Announcements',
		COMMENT_CATEGORY_ID: 'DIC_kwDOJlwoBM4CWpCu',
	},
};
 
module.exports = nextConfig;
const nextConfig = {
	env: {
		COMMENTS_REPO: 'ayoubkhial/nextjs-giscus-blog',
		COMMENTS_REPO_ID: 'R_kgDOJlwoBA',
		COMMENTS_CATEGORY: 'Announcements',
		COMMENT_CATEGORY_ID: 'DIC_kwDOJlwoBM4CWpCu',
	},
};
 
module.exports = nextConfig;

Update the values with your repository information. Feel free to add any variables you require to be dynamic based on your environments. If you prefer a single repository for all your environments, you can directly utilize the variables in your component without storing them.

Now, let's create a Comments component where we wrap the script provided by Giscus. First, create a new file comments.js (or .jsx, .tsx if you're using Typescript) under the components folder (or any folder you use to store your shared components).

In fact, there are two methods to inject the script into the HTML. Let's begin with the more generic DOM approach.

components/comments.js
'use client';
 
import { useEffect } from 'react';
 
const Comments = ({ repo, repoId, category, categoryId }) => {
	useEffect(() => {
		const script = document.createElement('script');
		const commentsDiv = document.getElementById('post-comments');
		script.async = true;
		script.setAttribute('src', 'https://giscus.app/client.js');
		script.setAttribute('data-repo', repo);
		script.setAttribute('data-repo-id', repoId);
		script.setAttribute('data-category', category);
		script.setAttribute('data-category-id', categoryId);
		script.setAttribute('data-mapping', 'pathname');
		script.setAttribute('data-strict', '0');
		script.setAttribute('data-reactions-enabled', '1');
		script.setAttribute('data-emit-metadata', '0');
		script.setAttribute('data-input-position', 'top');
		script.setAttribute('data-theme', 'preferred_color_scheme');
		script.setAttribute('data-lang', 'en');
		script.setAttribute('data-loading', 'lazy');
		script.setAttribute('crossorigin', 'anonymous');
		try {
			commentsDiv.appendChild(script);
		} catch (error) {
			console.error('Error while rendering giscus widget.', error);
		}
	}, []);
 
	return (
		<div id="post-comments">
			<h2>Comments</h2>
		</div>
	);
};
 
export default Comments;
components/comments.js
'use client';
 
import { useEffect } from 'react';
 
const Comments = ({ repo, repoId, category, categoryId }) => {
	useEffect(() => {
		const script = document.createElement('script');
		const commentsDiv = document.getElementById('post-comments');
		script.async = true;
		script.setAttribute('src', 'https://giscus.app/client.js');
		script.setAttribute('data-repo', repo);
		script.setAttribute('data-repo-id', repoId);
		script.setAttribute('data-category', category);
		script.setAttribute('data-category-id', categoryId);
		script.setAttribute('data-mapping', 'pathname');
		script.setAttribute('data-strict', '0');
		script.setAttribute('data-reactions-enabled', '1');
		script.setAttribute('data-emit-metadata', '0');
		script.setAttribute('data-input-position', 'top');
		script.setAttribute('data-theme', 'preferred_color_scheme');
		script.setAttribute('data-lang', 'en');
		script.setAttribute('data-loading', 'lazy');
		script.setAttribute('crossorigin', 'anonymous');
		try {
			commentsDiv.appendChild(script);
		} catch (error) {
			console.error('Error while rendering giscus widget.', error);
		}
	}, []);
 
	return (
		<div id="post-comments">
			<h2>Comments</h2>
		</div>
	);
};
 
export default Comments;

We can obtain the same outcome by utilizing the Giscus React component, a wrapper for the identical script. This approach appears more streamlined and seamlessly integrates with the React ecosystem.

First, you need to install @giscus/react package:

# Replace "npm" with your preferred package manager.
npm i @giscus/react
# Replace "npm" with your preferred package manager.
npm i @giscus/react

Then change the Comments component to use this package instead:

'use client';
 
import Giscus from '@giscus/react';
 
const Comments = ({ repo, repoId, category, categoryId }) => {
	return (
		<Giscus
			repo={repo}
			repoId={repoId}
			category={category}
			categoryId={categoryId}
			mapping="pathname"
			reactionsEnabled="1"
			emitMetadata="0"
			inputPosition="top"
			theme="preferred_color_scheme"
			lang="en"
			loading="lazy"
		/>
	);
};
 
export default Comments;
'use client';
 
import Giscus from '@giscus/react';
 
const Comments = ({ repo, repoId, category, categoryId }) => {
	return (
		<Giscus
			repo={repo}
			repoId={repoId}
			category={category}
			categoryId={categoryId}
			mapping="pathname"
			reactionsEnabled="1"
			emitMetadata="0"
			inputPosition="top"
			theme="preferred_color_scheme"
			lang="en"
			loading="lazy"
		/>
	);
};
 
export default Comments;

The Comments component accepts our environment variables as parameters. Since we're using a client component, we don't have direct access to those variables; therefore, we need to pass them when invoking this component from the BlogPost component, which is a server component. You can access the environment variables in a server component through process.env. Here's an example of what it may look like:

import Comment from '@/components/comment';
import dynamic from 'next/dynamic';
 
export default function BlogPost({ params }) {
	const { slug } = params;
 
	const Article = dynamic(() => import(`../../../content/${slug}.js`), {
		loading: () => <p>Loading...</p>,
	});
	const repo = process.env.COMMENTS_REPO;
	const repoId = process.env.COMMENTS_REPO_ID;
	const category = process.env.COMMENTS_CATEGORY;
	const categoryId = process.env.COMMENTS_CATEGORY_ID;
	return (
		<main>
			<div className="container">
				<article className="blog__content">
					<Article />
				</article>
				<Comment repo={repo} repoId={repoId} category={category} categoryId={categoryId} />
			</div>
		</main>
	);
}
import Comment from '@/components/comment';
import dynamic from 'next/dynamic';
 
export default function BlogPost({ params }) {
	const { slug } = params;
 
	const Article = dynamic(() => import(`../../../content/${slug}.js`), {
		loading: () => <p>Loading...</p>,
	});
	const repo = process.env.COMMENTS_REPO;
	const repoId = process.env.COMMENTS_REPO_ID;
	const category = process.env.COMMENTS_CATEGORY;
	const categoryId = process.env.COMMENTS_CATEGORY_ID;
	return (
		<main>
			<div className="container">
				<article className="blog__content">
					<Article />
				</article>
				<Comment repo={repo} repoId={repoId} category={category} categoryId={categoryId} />
			</div>
		</main>
	);
}

Based on your system's theme, the final result will be displayed in either dark or light mode.

Comment section using giscus and GitHub Discussions

Feel free to experience it live in the comment section of this article.

Advanced configuration

Content Security Policy

If you have a strict Content Security Policy, you will encounter a "violation of Content Security Policy" error in your browser. To fix that, you must modify it to include giscus.app within your policy's frame-src section.

const ContentSecurityPolicy = `
    default-src 'self' vercel.live;
    script-src 'self' 'unsafe-eval' 'unsafe-inline' vercel.live vitals.vercel-insights.com;
    style-src 'self' 'unsafe-inline';
    img-src * blob: data:;
    media-src 'none';
    frame-src giscus.app;
    connect-src *;
    font-src 'self';
`;
 
const securityHeaders = [
	{
		key: 'Content-Security-Policy',
		value: ContentSecurityPolicy.replace(/\n/g, ''),
	},
	// Other security headers
	// ...
];
 
const nextConfig = {
	env: {
		COMMENTS_REPO: 'ayoubkhial/nextjs-giscus-blog',
		COMMENTS_REPO_ID: 'R_kgDOJlwoBA',
		COMMENTS_CATEGORY: 'Announcements',
		COMMENTS_CATEGORY_ID: 'DIC_kwDOJlwoBM4CWpCu',
	},
	headers() {
		return [
			{
				source: '/(.*)',
				headers: securityHeaders,
			},
		];
	},
};
 
module.exports = nextConfig;
const ContentSecurityPolicy = `
    default-src 'self' vercel.live;
    script-src 'self' 'unsafe-eval' 'unsafe-inline' vercel.live vitals.vercel-insights.com;
    style-src 'self' 'unsafe-inline';
    img-src * blob: data:;
    media-src 'none';
    frame-src giscus.app;
    connect-src *;
    font-src 'self';
`;
 
const securityHeaders = [
	{
		key: 'Content-Security-Policy',
		value: ContentSecurityPolicy.replace(/\n/g, ''),
	},
	// Other security headers
	// ...
];
 
const nextConfig = {
	env: {
		COMMENTS_REPO: 'ayoubkhial/nextjs-giscus-blog',
		COMMENTS_REPO_ID: 'R_kgDOJlwoBA',
		COMMENTS_CATEGORY: 'Announcements',
		COMMENTS_CATEGORY_ID: 'DIC_kwDOJlwoBM4CWpCu',
	},
	headers() {
		return [
			{
				source: '/(.*)',
				headers: securityHeaders,
			},
		];
	},
};
 
module.exports = nextConfig;

Alternatively, you can add it as a fallback within the default-src section.

Giscus configuration file

You can configure two more options for your giscus.app. For that, create a giscus.json file under the root directory.

{
	// Change the default comment ordering by configuring giscus.app
	// default: oldest
    "defaultCommentOrder": "newest",
 
	// restrict from which websites people can load your comments (a security layer)
	"origins": ["https://www.ayoubkhial.com"],
	// or use a regular expression instead
	"originsRegex": ["https?://([a-z0-9]+[.])ayoubkhial[.]com"]
}
{
	// Change the default comment ordering by configuring giscus.app
	// default: oldest
    "defaultCommentOrder": "newest",
 
	// restrict from which websites people can load your comments (a security layer)
	"origins": ["https://www.ayoubkhial.com"],
	// or use a regular expression instead
	"originsRegex": ["https?://([a-z0-9]+[.])ayoubkhial[.]com"]
}

Read more about Giscus advanced features in the Adavnced Usage page.

Conclusion

Giscus provides a neat and reliable solution for managing your blog comments, eliminating the need to maintain your commenting system. It's a perfect fit, particularly for tech blogs 💎.

You can find the complete code source in this repository; feel free to give it a star ⭐️.