Skip to main content

Adding Comments to Docusaurus

· 14 min read

Static site generators like Hugo and Docusaurus have different levels of support for comment systems. The current version of Docusaurus does not have a built-in commenting system. With Hugo, it depends on whether the theme you are using has added support for additional commenting systems. It could have Disqus, Giscus, or Utterances to name a few. With any of the static site generators, you can customize and add your favorite comment system to it. This involves adding some code and specifying in the front matter that you want comments enabled. Here is how I added comments to Docusaurus for my blog.

Swizzling

To enhance the layout of Docusaurus we need to modify the theme component with our changes. In Hugo, this meant we usually just copied the file from the theme and made the changes to the file. When you generate the site, Hugo would use a lookup order for partial templates that would first check in the layouts folder and then would check in the themes folder. The same concept is happening in Docusaurus where it chooses the file with the higher precedence. If there is a file in the /src/theme folder it will use that version of the file. If not it will use the one that is in the imported theme. The main difference is we can use a command named Swizzle to help with generating the component.

note

Using Swizzle is not a requirement and you can still copy and paste the file you want from another source or even create it from scratch.

You can run the Swizzle command without any arguments and it will launch a menu system. This is a good method if you are not sure what file you are looking for. For example, I can run the command and choose the classic theme and then I get a list of components. For now, I only want to put comments on blog posts. If I extend my site to include docs and want comments for the docs then I will need to make the changes there as well.

npm run swizzle

We are presented with a menu that will vary depending on how many plugins your project is using.

> ds-blog-source@0.0.0 swizzle
> docusaurus swizzle

? Select a theme to swizzle: » - Use arrow-keys. Return to submit.
> @docusaurus/theme-classic
@docusaurus/plugin-debug
[Exit]

We pick the @docusaurus/theme-classic and then we are presented with a list of components to pick from. We want to pick the BlogPostItem (Unsafe) and click enter. This is not the only choice. Originally I had this configured to use the Footer file since that was closer to what I had done with Hugo. BlogPostItem is the header, content, and footer all inside of a container.

√ Select a theme to swizzle: » @docusaurus/theme-classic
?
Select or type the component to swizzle.
* = not safe for all swizzle actions
»
↑ BlogListPage (Unsafe)
BlogListPaginator (Unsafe)
BlogPostItem (Unsafe)
BlogPostItem/Container (Unsafe)
BlogPostItem/Content (Unsafe)
> BlogPostItem/Footer (Unsafe)
BlogPostItem/Footer/ReadMoreLink (Unsafe)
BlogPostItem/Header (Unsafe)
BlogPostItem/Header/Author (Unsafe)
↓ BlogPostItem/Header/Authors (Unsafe)

Now we have a choice between Wrap and Eject. I choose to Wrap the file.

? Which swizzle action do you want to do? » - Use arrow-keys. Return to submit.
> Wrap (Unsafe)
Eject (Unsafe)
[Exit]

Since it is marked as unsafe we will be presented with this message and need to select YES:

√ Which swizzle action do you want to do? » Wrap (Unsafe)
[WARNING]
Swizzle action wrap is unsafe to perform on BlogPostItem.
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]

After confirming the file will be created:

Created wrapper of BlogPostItem from @docusaurus/theme-classic in
- "D:\DocusaurusBlog\src\theme\BlogPostItem\index.js"

Wrapping or Ejecting

You have two options when using components.

  • Wrap
  • Eject

Wrapping the code references the original component and then allows you to add your code to it. This is the preferred method because if the component changes you will still pick up those changes and add to it. The other option is to eject the file which places the original file without any additional code. This is not the preferred method since if the component gets updated we will need to update our file with those changes plus maintain what we have added to the file. Eject would be the option if we want to change how a component was implemented versus just appending some extra code to it.

If you know the exact file you want and whether you want to wrap or eject the file you can run swizzle with those arguments.

npm run swizzle @docusaurus/theme-classic BlogPostItem -- --wrap --danger

This says that I want to select the theme of @docusaurus/theme-classic and use the component BlogPostItem. I am choosing to wrap (--wrap) the component and since this method for this component is marked as unsafe I need to override it using --danger. If you are using TypeScript you will need to add --typescript at the end of the command.

But wait, what is this talk about unsafe components?

3 levels of Safety

Here are the 3 levels of safety:

  • Safe - no breaking changes should happen with a major version
  • Unsafe - theme implementation detail and could break with a theme minor version
  • Forbidden - not designed to be swizzled

As you can see we can swizzle components even if they are marked as unsafe knowing that we may need to make changes in the future as Docusaurus is updated.

Now that I ran the command to swizzle the component I will now see a folder src/theme/BlogPostItem containing an index.js file.

caution

If you swizzle the same component after making changes it will replace the file losing any modifications that have been made

Revert Changes

You can delete the folders and files that were swizzled and Docusaurus will go back to using the files in the theme component. Now that we have swizzled the file we need, we need to go about setting up the Giscus app. If you are curious about the components that make up the theme, the location may vary but will be similar to node_modules/@docusaurus/theme-classic/src/theme.

Giscus

For the comment system, I went with giscus. Utterances is another one I have used. The main difference is that Utterances uses issues in Github while Giscus uses discussions. There is even a link in the documentation on how to migrate systems that use Github Issues to discussions.

Install the App to Github

You need to create a repository in Github that will store the comments. I like to create one repository for each site that has comments. It needs to be public and I initialize it with a readme file. Github Repo
After it is created we need to enable discussions on the repository. To enable discussions navigate to Settings for the repository. Then go to General->Features. Check the box next to Discussions. You do not need to click the button "Set Up Discussions" (that button just creates an initial discussion). Enable Discussions Now we need to go install the app to github and configure our settings on the giscus site. Under the configuration section, it has the link to install the app to GitHub. Once you click to install the app you will have to sign in to Github. Then click the Configure button. I only install it to select repositories that are going to be used for comments. In the future, you can go to Settings in Github and under the Integrations->Applications screen and configure the app including suspending the app or uninstalling it. Now we can fill out the rest of the configuration section on the giscus site that will populate a script file with the values we need.

If you have skipped a step in creating the repository you will get this message:
Giscus Error
If that happens make sure:

  • the repository is public
  • the giscus app is installed on that repository
  • the discussion feature is turned on

This means we have configured the GitHub repository correctly. App Success

Continue to fill out the configuration section. As we pick the different selections it is building a script file that we can use as a template. I usually go with Mapping of Title, Discussion Category of Announcements, only search category, Enable Reactions, Lazy Load, and for theme - I bounce between the light and dark options. We control all these settings from our Docusaurus files so it is not difficult to switch options later. Note that when we are filling out this website it is building a template for us to use. It is not configuring the app in GitHub. You need to copy the script file so that we can use it to configure our settings. Once you have done all the steps to configure giscus I usually copy the script to a file and save it for future reference. You will need those settings shortly to configure your installation.

Install giscus React component

Then you need to install the giscus react component - Giscus React Component. This command will install the package:

npm i @giscus/react

It will install the package in node_modules/@giscus.

Adding comments

A requirement I had for how I wanted comments to work is that in the front matter of each post, I wanted a field named comments that can be set to true or false for each post. That way if I decide to not open comments for certain posts or if I decide to stop using comments I can just set it to false. Now we can go back to the file we swizzled - src/theme/BlogPostItem/index.js. This is the part that can differ depending on how much you want to separate the code.

Earlier we copied out the giscus script with all the key-value pairs. It is time to use those but since we are using the React component the Prop Names have to be written differently. Remove the data- prefix, remove the dashes and write them in camelCase and then we can put everything that is needed in the one file we swizzled:

src/theme/BlogPostItem/index.js
import React from 'react'; // part of swizzled file
import BlogPostItem from '@theme-original/BlogPostItem'; // part of swizzled file
import { useBlogPost } from "@docusaurus/theme-common/internal"; // need for isBlogPostPage and access to frontmatter-comments
import Giscus from '@giscus/react'; //needed for Giscus component

export default function BlogPostItemWrapper(props) {
const { metadata, isBlogPostPage} = useBlogPost();
const { frontMatter} = metadata
const { comments = false } = frontMatter; //only display comments if this is set to true

return (
<>
<BlogPostItem {...props} />
{comments && isBlogPostPage && (
<Giscus
id = "comments"
repo = "thedaxshepherd/blog-comments-giscus"
repoId = "R2D2_Ixejvg"
category = "Announcements"
categoryId = "DMN_kwIxejvs4CTlRG"
mapping = "title"
strict = "1"
reactionsEnabled = "1"
emitMetadata = "0"
inputPosition = "top"
theme = "light"
lang = "en"
loading = "lazy"
crossorigin = "anonymous"
/>
)}
</>
)
}

This will render the original BlogPostItem regardless and only render the comments part if comments are set to true and we are on a BlogPostPage. This works great and if our goal is to just get a working version up and running. I think we can improve on this if we want more flexibility in the future. I went a step further and use a React component to implement the code to generate the comments.

src/theme/BlogPostItem/index.js
import React from 'react'; // part of default file
import BlogPostItem from '@theme-original/BlogPostItem'; // part of default file
import Comments from '@site/src/components/GiscusComments';
import { useBlogPost } from "@docusaurus/theme-common/internal";

export default function BlogPostItemWrapper(props) {
const { metadata, isBlogPostPage} = useBlogPost();
const { frontMatter} = metadata
const { comments = false } = frontMatter;

return (
<>
<BlogPostItem {...props} />
{comments && isBlogPostPage && (
<Comments />
)
}
</>
)
}

Much like using a function or method I now can abstract out the logic for that piece to a separate file. The conditional is in this file so that I am not returning a blank component if the conditions are not met. One reason I am using a separate component is that it gives me the flexibility to switch out the comment component. To do that I would just change my import statement to point to a different component such as this: import Comments from '@site/src/components/UtterancesComments'. If I add the Documentation (Docs) feature, it will be easy to add the comment component to that part of the site without duplicating code. Now you need to create the component file in the src/components folder:

src/components/GiscusComments.js
import React from "react";
import Giscus from '@giscus/react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';

export default function GiscusComments()
{
const { siteConfig } = useDocusaurusContext();

return (
<Giscus
id = "comments"
repo = "thedaxshepherd/blog-comments-giscus"
repoId = "R2D2_Ixejvg"
category = "Announcements"
categoryId = "DMN_kwIxejvs4CTlRG"
mapping = "title"
strict = "1"
reactionsEnabled = "1"
emitMetadata = "0"
inputPosition = "top"
theme = "light"
lang = "en"
loading = "lazy"
crossorigin = "anonymous"
/>
)
}

This is another place where how you implement this depends on how much you want to abstract the data. Instead of putting the values in this file, I have created a set of custom fields in the docusaurus.config.js file. That way if I want to change something such as the theme I can just change it in the config file and not come back to this file.

src/components/GiscusComments.js
import React from "react";
import Giscus from '@giscus/react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';

export default function GiscusComments()
{
const { siteConfig } = useDocusaurusContext();

return (
<Giscus
id = {`${siteConfig.customFields.Giscus_Id}`}
repo = {`${siteConfig.customFields.Giscus_organizationName}/${siteConfig.customFields.Giscus_projectNameComments}`}
repoId = {`${siteConfig.customFields.Giscus_repoId}`}
category = {`${siteConfig.customFields.Giscus_category}`}
categoryId = {`${siteConfig.customFields.Giscus_categoryId}`}
mapping = {`${siteConfig.customFields.Giscus_mapping}`}
strict = {`${siteConfig.customFields.Giscus_strict}`}
reactionsEnabled = {`${siteConfig.customFields.Giscus_reactionsEnabled}`}
emitMetadata = {`${siteConfig.customFields.Giscus_emitMetadata}`}
inputPosition = {`${siteConfig.customFields.Giscus_inputPosition}`}
theme = {`${siteConfig.customFields.Giscus_theme}`}
lang = {`${siteConfig.customFields.Giscus_lang}`}
loading = {`${siteConfig.customFields.Giscus_loading}`}
crossorigin = {`${siteConfig.customFields.Giscus_crossorigin}`}
/>
)
}

The two pieces that are needed in the above file to use the siteConfig.customFields are the following:

import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
const { siteConfig } = useDocusaurusContext();

You can name your custom fields whatever you want as long as you create the matching field in the config file. In the Docusaurus config file, I define the custom fields and their values.

docusaurus.config.js
const config = {
title: 'The DAX Shepherd',
...
customFields: {
Giscus_organizationName: 'thedaxshepherd',
Giscus_Id: 'comments',
Giscus_projectNameComments: 'thedaxshepherd/blog-comments-giscus',
Giscus_repoId: 'R2D2_Ixejvg',
Giscus_theme: 'light',
...
},
...

Having all the values in the config file makes it easy to switch values such as the theme without leaving the configuration file. I have taken it one step further and I use React environment variables in the config file. This allows me to use the environment variables to have different configuration settings including a different repository for a test environment.

Customizing with CSS

Lastly, there are many options to customize what the comment component looks like. The simplest would be changing the theme that it is using. Another way is to use CSS to add things like a scrollable window. There are more details in the documentation for the component. Using their example this is how you would enable a scrollable widget.

src/components/GiscusComments.js
...
return (
<div class="comments-container">
<Giscus
id = {`${siteConfig.customFields.Giscus_Id}`}
repo = {`${siteConfig.customFields.Giscus_organizationName}/${siteConfig.customFields.Giscus_projectNameComments}`}
...
/>
</div>
)

We need to add the matching code to our CSS file. If we wanted we could add a border around the widget or various other customizations using CSS.

src/css/custom.css
.comments-container {
max-height: 640px;
overflow-y: scroll;
}

This is what it looks like with a scrollbar and multiple comments. Scrollbar

Future Versions

Future versions of Docusaurus could have commenting built in. By manually adding it we have the flexibility in choosing what commenting system to use and how it is configured. One of the benefits of Docusaurus is that you can get it set up quickly and start using it but it still has the flexibility to customize later.