Skip to main content

How to add a comments section to doc pages and blog entries using giscus

Daniel Farlow
Software Engineer

This post details how to outfit your Docusaurus site's doc pages and blog posts with a comments section using giscus.

info

This blog post was originally inspired by the post on Canny, a site meant for lisitng Docusaurus feature requests. Specifically, a blog post by Dipak Parmar was used us the starting point.

Note: All examples in this post were run using Docusaurus 2.1.0.

Follow the steps below to add a comments section similar to what appears at the end of this post. Since what you are reading is a blog entry, it is clear that comments have been enabled for blog posts on this site. But comments can also be enabled for documentation entries such as this site's introduction page. Choose what works best for you according to the steps below.

Warnings you may encounter while swizzling

As Docusaurus itself notes: swizzling allows deeper site customizations. The swizzling customizations we are going to make are very minor. We do not need to worry much about "copying a large amount of internal code" which we will then have to maintain ourselves. If the Docusaurus maintainers change something at some point that interferes with our changes, then we can easily re-swizzle and update components to mesh cleanly with our original updates.

To get an overview of all the themes and components available to swizzle, run the following:

npm run swizzle -- --list

We will be making updates to DocItem/Layout (for the docs) and BlogPostItem/Content (for the blog), both deemed components "unsafe" to make changes to. But, as noted above, our changes will be minimal and these components can easily be re-swizzled if we ever run into problems in the future.

Giscus

What is giscus? Quick answer according to its homepage:

A comments system powered by GitHub Discussions. Let visitors leave comments and reactions on your website via GitHub! Heavily inspired by utterances.

Essentially, giscus will make it possible for us to let users comment on our docs/blog without having to use some sort of database.

How giscus works

As noted on the homepage for giscus:

When giscus loads, the GitHub Discussions search API is used to find the Discussion associated with the page based on a chosen mapping (URL, pathname, <title>, etc.). If a matching discussion cannot be found, the giscus bot will automatically create a discussion the first time someone leaves a comment or reaction.

To comment, visitors must authorize the giscus app [on GitHub] to post on their behalf using the GitHub OAuth flow. Alternatively, visitors can comment on the GitHub Discussion directly. You can moderate the comments on GitHub.

Since doc pages and blog entries will be unique by path name, our chosen mapping for giscus will be pathname.

Giscus installation and configuration

Follow the steps outlined below to install and configure giscus to your liking.

Start by installing @giscus/react as a dev dependency:

npm install -D @giscus/react

Enable comments for doc pages

Start by swizzling the DocItem/Layout component:

npm run swizzle @docusaurus/theme-classic DocItem/Layout -- --eject

You will likely encounter a prompt that looks as follows (as of Oct. 27, 2022, at least):

> software-development-handbook@0.0.0 swizzle
> docusaurus swizzle "@docusaurus/theme-classic" "DocItem/Layout" "--eject"

[WARNING]
Swizzle action eject is unsafe to perform on DocItem/Layout.
It is more likely to be affected by breaking changes in the future
If you want to swizzle it, use the `--danger` flag, or confirm that you understand the risks.

? Do you really want to swizzle this unsafe internal component? › - Use arrow-keys. Return to submit.
❯ NO: cancel and stay safe
YES: I know what I am doing!
[Exit]

Press the down arrow to point to the highlighted line above to confirm the swizzling directive. Two files will then be "ejected" to /src/theme/DocItem/Layout in your project, namely index.js and styles.modules.css (you do not need to modify this file unless you want to change the default styling). These originally ejected files are shown below in the two tabs on the right for reference. The first tab includes the modifications we need to make to the index.js file in order for giscus to work with our doc pages (all changes/additions from the originally swizzled file have been highlighted):

/src/theme/DocItem/Layout/index.js
import React from 'react';
import clsx from 'clsx';
import { useWindowSize } from '@docusaurus/theme-common';
import { useDoc } from '@docusaurus/theme-common/internal';
import DocItemPaginator from '@theme/DocItem/Paginator';
import DocVersionBanner from '@theme/DocVersionBanner';
import DocVersionBadge from '@theme/DocVersionBadge';
import DocItemFooter from '@theme/DocItem/Footer';
import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
import DocItemContent from '@theme/DocItem/Content';
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
import styles from './styles.module.css';
import Giscus from '@giscus/react';
import { useColorMode } from '@docusaurus/theme-common';
/**
* Decide if the toc should be rendered, on mobile or desktop viewports
*/
function useDocTOC() {
const { frontMatter, toc } = useDoc();
const windowSize = useWindowSize();
const hidden = frontMatter.hide_table_of_contents;
const canRender = !hidden && toc.length > 0;
const mobile = canRender ? <DocItemTOCMobile /> : undefined;
const desktop =
canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? (
<DocItemTOCDesktop />
) : undefined;
return {
hidden,
mobile,
desktop,
};
}
export default function DocItemLayout({ children }) {
const docTOC = useDocTOC();
const { colorMode } = useColorMode();
const giscus = (
<React.Fragment>
<hr />
<br></br>
<Giscus
id="comments"
repo="farlowdw/software-development-handbook"
repoId="R_kgDOHmzzBQ"
category="Announcements"
categoryId="DIC_kwDOHmzzBc4CSLOr"
mapping="pathname"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="top"
theme={colorMode}
lang="en"
loading="lazy"
/>
</React.Fragment>
)
return (
<div className="row">
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
<DocVersionBanner />
<div className={styles.docItemContainer}>
<article>
<DocBreadcrumbs />
<DocVersionBadge />
{docTOC.mobile}
<DocItemContent>{children}</DocItemContent>
<DocItemFooter />
</article>
<DocItemPaginator />
{giscus}
</div>
</div>
{docTOC.desktop && <div className="col col--3">{docTOC.desktop}</div>}
</div>
);
}

The Giscus component comes from our work in the giscus installation and configuration section. The hr and br tags included above this component are merely to make the comments section more appealing from a presentational standpoint. Do what works best for you.

Congratulations! You should now have comments enabled for your doc pages.

Enable comments for blog entries

Start by swizzling the BlogPostItem/Content component:

npm run swizzle @docusaurus/theme-classic BlogPostItem/Content -- --eject

You will likely encounter a prompt that looks as follows (as of Oct. 27, 2022, at least):

> software-development-handbook@0.0.0 swizzle
> docusaurus swizzle "@docusaurus/theme-classic" "BlogPostItem/Content" "--eject"

[WARNING]
Swizzle action eject is unsafe to perform on BlogPostItem/Content.
It is more likely to be affected by breaking changes in the future
If you want to swizzle it, use the `--danger` flag, or confirm that you understand the risks.

? Do you really want to swizzle this unsafe internal component? › - Use arrow-keys. Return to submit.
❯ NO: cancel and stay safe
YES: I know what I am doing!
[Exit]

Press the down arrow to point to the highlighted line above to confirm the swizzling directive. A single file will then be "ejected" to /src/theme/BlogPostItem/Content in your project, namely index.js. The originally ejected file is shown below in the tab on the right for reference. The first tab includes the modifications we need to make to the index.js file in order for giscus to work with our blog entries (all changes/additions from the originally swizzled file have been highlighted):

/src/theme/BlogPostItem/Content/index.js
import React from 'react';
import clsx from 'clsx';
import {blogPostContainerID} from '@docusaurus/utils-common';
import {useBlogPost} from '@docusaurus/theme-common/internal';
import MDXContent from '@theme/MDXContent';
import Giscus from '@giscus/react';
import { useColorMode } from '@docusaurus/theme-common';
export default function BlogPostItemContent({children, className}) {
const {isBlogPostPage} = useBlogPost();
const { colorMode } = useColorMode();
const giscus = (
<React.Fragment>
<hr />
<br></br>
<Giscus
id="comments"
repo="farlowdw/software-development-handbook"
repoId="R_kgDOHmzzBQ"
category="Announcements"
categoryId="DIC_kwDOHmzzBc4CSLOr"
mapping="pathname"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="top"
theme={colorMode}
lang="en"
loading="lazy"
/>
</React.Fragment>
)
return (
<div
// This ID is used for the feed generation to locate the main content
id={isBlogPostPage ? blogPostContainerID : undefined}
className={clsx('markdown', className)}
itemProp="articleBody">
<MDXContent>
{children}
{isBlogPostPage && giscus}
</MDXContent>
</div>
);
}

The Giscus component comes from our work in the giscus installation and configuration section. The hr and br tags included above this component are merely to make the comments section more appealing from a presentational standpoint. Do what works best for you.

Congratulations! You should now have comments enabled for your blog entries.

Selectively disable comments

As the Docusaurus docs note, we can take advantage of useLocation to get the current page's location:

import {useLocation} from '@docusaurus/router';

export function someFunction() {
// React router provides the current component's route, even in SSR
const location = useLocation();
...
}

This means we can use location.pathname to programmatically determine whether or not a giscus comments section should be made available for specific doc pages or blog entries based on the paths for those entries.

But where should such paths be specified? Here are two sensible options:

  1. Directly within swizzled component: The paths could be specified within each swizzled component directly; for example, doc paths for which we do not want giscus comment sections could be specified in /src/theme/DocItem/Layout/index.js directly.
  2. Within Docusaurus config (preferred): The first option may seem like a good choice at first, but it quickly becomes problematic if we ever want to revert back to the current DocItem or BlogPostItem being maintained by the Docusaurus team and re-swizzle them to use giscus comment sections. The code shown in previous sections of this article would not be problematic to add back to freshly swizzled components, but adding all of the paths back might be cumbersome depending on how many were added in the first place.

A convenient solution is to make use of the useDocusaurusContext hook, which gives us access to the siteConfig object from docusaurus.config.js. How does this help? As the docs note:

Docusaurus guards docusaurus.config.js from unknown fields. To add a custom field, define it on customFields.

Hence, we can add a customFields property to our exported config object in docusaurus.config.js, which will then be accessible as a property on the siteConfig object, which is made available by means of the useDocusaurusContext hook.

The takeaway is that we can add our paths to the customFields object, which will then not only be accessible globally but also, most importantly, within whatever components we want to swizzle:

customFields: {
forbiddenGiscusDocPaths: [
'/docs/tutorials/intro'
],
forbiddenGiscusBlogPaths: [
'/blog/2022/10/27/giscus-comments'
],
}

The tabs below indicate how we can build upon our previous work to achieve this (line changes are highlighted):

/src/theme/DocItem/Layout/index.js
import React from 'react';
import clsx from 'clsx';
import { useWindowSize } from '@docusaurus/theme-common';
import { useDoc } from '@docusaurus/theme-common/internal';
import DocItemPaginator from '@theme/DocItem/Paginator';
import DocVersionBanner from '@theme/DocVersionBanner';
import DocVersionBadge from '@theme/DocVersionBadge';
import DocItemFooter from '@theme/DocItem/Footer';
import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
import DocItemContent from '@theme/DocItem/Content';
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
import styles from './styles.module.css';
import Giscus from '@giscus/react';
import { useColorMode } from '@docusaurus/theme-common';
import { useLocation } from '@docusaurus/router';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
/**
* Decide if the toc should be rendered, on mobile or desktop viewports
*/
function useDocTOC() {
const { frontMatter, toc } = useDoc();
const windowSize = useWindowSize();
const hidden = frontMatter.hide_table_of_contents;
const canRender = !hidden && toc.length > 0;
const mobile = canRender ? <DocItemTOCMobile /> : undefined;
const desktop =
canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? (
<DocItemTOCDesktop />
) : undefined;
return {
hidden,
mobile,
desktop,
};
}
export default function DocItemLayout({ children }) {
const docTOC = useDocTOC();
const { colorMode } = useColorMode();
const location = useLocation();
const {siteConfig} = useDocusaurusContext();
const {forbiddenGiscusDocPaths} = siteConfig.customFields;
const giscus = (
<React.Fragment>
<hr />
<br></br>
<Giscus
id="comments"
repo="farlowdw/software-development-handbook"
repoId="R_kgDOHmzzBQ"
category="Announcements"
categoryId="DIC_kwDOHmzzBc4CSLOr"
mapping="pathname"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="top"
theme={colorMode}
lang="en"
loading="lazy"
/>
</React.Fragment>
)
return (
<div className="row">
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
<DocVersionBanner />
<div className={styles.docItemContainer}>
<article>
<DocBreadcrumbs />
<DocVersionBadge />
{docTOC.mobile}
<DocItemContent>{children}</DocItemContent>
<DocItemFooter />
</article>
<DocItemPaginator />
{!forbiddenGiscusDocPaths.includes(location.pathname) && giscus}
</div>
</div>
{docTOC.desktop && <div className="col col--3">{docTOC.desktop}</div>}
</div>
);
}

The changes above mean that all doc pages will have a giscus comments section except the doc page at the following path:

/docs/tutorials/intro

To prevent comments for other paths, simply add these paths to the forbiddenGiscusDocPaths array.