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.
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:
As a result, Giscus offers numerous advantages compared to other services:
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.
Giscus bot will automatically create a discussion on the first comment or reaction if a matching discussion cannot be found.
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.
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.
Creating a separate repository for discussions is often recommended, as it allows you the flexibility to direct your website to a different repository without the need to relocate your discussions.
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.
To inform your community about the intended use of Discussions, you can create a welcome page by clicking Set up 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.
Choose the repository in which you wish to utilize GitHub Discussions. In my case, it's nextjs-giscus-blog.
I highly recommend granting access only to the repositories that require Giscus.
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.
If all criteria are met, you will see the message "Success! This repository meets all of the above criteria." under the repository input. If you see an error message, re-check all the steps above.
Make sure to enable the "Load the comments lazily" option. This ensures that Giscus widget is loaded only when the visitor scrolls down to the section where the Giscus instance is located on the page, typically near the bottom.Enabling this option can enhance the visitor experience.
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>
Alternatively, You can retrieve your repository information, such as
repoId
andcategoryId
, using the GitHub GraphQL API Explorer instead.
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 yournext.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;
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.
'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;
Since we use browser features like React Hooks and DOM, you must mark the component as a client component.
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
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;
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 fromthe 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>
);
}
Instead of using jsx files to store the post content in the content folder, it is generally preferred and customary to utilize MDX format for storing your content. I'm using the jsx here for simplicity.
During the build time, Next.js will inject values into the variables. However, it is important to note that you cannot utilize destructuring to access them due to the nature of Webpack. If you opt for the traditionalapproach of storing environment variables in a
.env
file, you can utilize destructuring to access them.
Based on your system's theme, the final result will be displayed in either dark or light mode.
Feel free to experience it live in the comment section of this article.
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;
Alternatively, you can add it as a fallback within the default-src
section.
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"]
}
Read more about Giscus advanced features on the Advanced Usage page.
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 ⭐️.
Also published here.