Skip to main content
Adding Comments to Hugo Blog with Giscus

Adding Comments to Hugo Blog with Giscus

· loading · loading ·
gunyoung.Park
Author
gunyoung.Park
Always curious, always exploring new tech
Table of Contents
Hugo - This article is part of a series.
Part 3: This Article

What is Giscus?
#

Giscus is an open-source comment system that uses GitHub Discussions as its backend.

Key Features
#

  • βœ… Completely Free (leverages GitHub features)
  • βœ… No Server Required (GitHub handles everything)
  • βœ… Full Markdown Support (code blocks, images, tables, etc.)
  • βœ… Reactions (πŸ‘, ❀️, πŸ˜„, etc.)
  • βœ… GitHub Notifications (get notified when comments are posted)
  • βœ… Dark Mode (auto-syncs with blog theme)
  • βœ… Data Ownership (stored in your repository)

Differences from Utterances
#

FeatureGiscusUtterances
BackendGitHub DiscussionsGitHub Issues
Reactionsβœ…βŒ
Nested Repliesβœ… (nested)⚠️ (flat)
Comment Sortingβœ…βš οΈ
Best ForCommentsIssue tracking

Conclusion: Giscus is the superior choice over Utterances.


Prerequisites
#

Requirements
#

  1. GitHub account
  2. Public GitHub repository (your blog repository)
  3. Hugo + Blowfish theme

Limitations
#

  • ⚠️ Public repositories only (Private repositories have limited Discussions functionality)
  • ⚠️ GitHub account required (no anonymous comments)

Step 1: Enable GitHub Discussions
#

1.1 Navigate to Repository Settings
#

  1. Go to your blog repository on GitHub

    Example: https://github.com/0AndWild/0AndWild.github.io
    
  2. Click the Settings tab

1.2 Enable Discussions
#

  1. Scroll down to find the Features section

  2. Check the Discussions checkbox βœ…

  3. It will save automatically

1.3 Verify
#

Confirm that the Discussions tab appears at the top of your repository

Code | Issues | Pull requests | Discussions | ← Newly created!

Step 2: Install Giscus App
#

2.1 Install Giscus GitHub App
#

  1. Visit https://github.com/apps/giscus

  2. Click the Install button

  3. Choose permission scope:

    • All repositories (all repositories)
    • Only select repositories (specific repositories - recommended)
  4. Select your blog repository:

    0AndWild/0AndWild.github.io
    
  5. Click Install

2.2 Verify Permissions
#

Giscus requests the following permissions:

  • βœ… Read access to discussions (read discussions)
  • βœ… Write access to discussions (write discussions)
  • βœ… Read access to metadata (read metadata)

Step 3: Generate Giscus Configuration
#

3.1 Visit Giscus Website
#

Go to https://giscus.app

3.2 Connect Repository
#

Enter in the Repository section:

0AndWild/0AndWild.github.io

You should see a success message below:

βœ… Success! This repository meets all criteria.

If you see an error:

  • Verify Discussions is enabled
  • Verify Giscus App is installed
  • Verify the repository is Public

3.3 Page ↔️ Discussion Mapping
#

Choose in the Discussion Mapping section:

Recommended: pathname (path name)#

Mapping: Select pathname

Each blog post’s path becomes the Discussion title.

Example:

  • Post: /posts/giscus-guide/
  • Discussion title: posts/giscus-guide

Alternatives:
#

  • URL: Uses full URL (problematic if domain changes)
  • title: Uses post title (problematic if title changes)
  • og:title: OpenGraph title
  • specific term: Manually specified

Recommendation: Use pathname

3.4 Select Discussion Category
#

Choose from the Discussion Category dropdown:

Recommended: Announcements#

Category: Select Announcements

Characteristics:

  • Only admins can create new Discussions
  • Anyone can comment
  • Ideal for blog posts

Alternative: General
#

  • Anyone can create Discussions
  • More open

Recommendation: Announcements (best for blogs)

3.5 Feature Selection
#

Enable Reactions
#

βœ… Enable reactions

Users can react with πŸ‘, ❀️, πŸ˜„, etc.

Emit Metadata
#

β–‘ Emit metadata (recommended to leave unchecked)

Unnecessary feature, better to keep it off

Comment Input Position
#

βšͺ Above comments
βšͺ Below comments (recommended)

Recommendation: Below comments

  • Encourages users to read existing comments first

Lazy Loading
#

βœ… Lazy loading

Improves page load speed (recommended)

3.6 Theme Selection
#

Recommended: preferred_color_scheme#

Theme: preferred_color_scheme

Behavior:

  • Automatically switches based on user’s system settings
  • Dark mode ↔️ Light mode automatic

Alternatives:
#

  • light: Always light theme
  • dark: Always dark theme
  • transparent_dark: Transparent dark
  • Other GitHub themes

Recommendation: preferred_color_scheme (auto-switching)

3.7 Language Setting
#

Language: en (English)

Step 4: Copy Generated Code
#

4.1 Copy Script
#

Copy the generated code from the Enable giscus section at the bottom of the page:

<script src="https://giscus.app/client.js"
        data-repo="0AndWild/0AndWild.github.io"
        data-repo-id="R_kgDOxxxxxxxx"
        data-category="Announcements"
        data-category-id="DIC_kwDOxxxxxxxx"
        data-mapping="pathname"
        data-strict="0"
        data-reactions-enabled="1"
        data-emit-metadata="0"
        data-input-position="bottom"
        data-theme="preferred_color_scheme"
        data-lang="en"
        data-loading="lazy"
        crossorigin="anonymous"
        async>
</script>

4.2 Important Values
#

  • data-repo-id: Repository unique ID (auto-generated)
  • data-category-id: Category unique ID (auto-generated)

These values are unique to your repository, so you must use the code generated from the Giscus website.


Step 5: Integrate with Blowfish Theme
#

5.1 Create Directory
#

From the terminal, navigate to your blog’s root directory:

mkdir -p layouts/partials

5.2 Create comments.html File
#

touch layouts/partials/comments.html

Or create directly in your IDE/editor:

layouts/
  └── partials/
      └── comments.html  ← Create new

5.3 Insert Giscus Code
#

Add the following content to layouts/partials/comments.html:

<!-- Giscus Comment System -->
<script src="https://giscus.app/client.js"
        data-repo="0AndWild/0AndWild.github.io"
        data-repo-id="R_kgDOxxxxxxxx"
        data-category="Announcements"
        data-category-id="DIC_kwDOxxxxxxxx"
        data-mapping="pathname"
        data-strict="0"
        data-reactions-enabled="1"
        data-emit-metadata="0"
        data-input-position="bottom"
        data-theme="preferred_color_scheme"
        data-lang="en"
        data-loading="lazy"
        crossorigin="anonymous"
        async>
</script>

⚠️ Important: Replace the data-repo-id and data-category-id values with your own values!

5.4 Configure params.toml
#

Open config/_default/params.toml and add to the [article] section:

[article]
  showComments = true  # Add or verify this line
  # ... other settings

If the showComments entry already exists, make sure it’s set to true.


Step 6: Local Testing
#

6.1 Run Hugo Server
#

hugo server -D

6.2 Verify in Browser
#

http://localhost:1313

The Giscus comment widget should appear at the bottom of post pages.

6.3 Write Test Comment
#

  1. Click Sign in with GitHub button
  2. Authorize GitHub OAuth
  3. Write a test comment
  4. Verify the comment displays

6.4 Check GitHub Discussions
#

  1. GitHub repository β†’ Discussions tab
  2. Verify a new Discussion was created in the Announcements category
  3. Verify the Discussion title matches the post path

Step 7: Deploy
#

7.1 Commit to Git
#

git add layouts/partials/comments.html
git add config/_default/params.toml
git commit -m "Add Giscus comments system"

7.2 Push to GitHub
#

git push origin main

7.3 Check GitHub Actions
#

GitHub Actions will automatically build and deploy.

Check deployment status:

GitHub repository β†’ Actions tab

7.4 Verify Deployed Site
#

https://0andwild.github.io

Verify the comment widget displays correctly on post pages.


Advanced Configuration
#

Dynamic Dark Mode and Language Setting (Recommended)#

A complete solution to make Giscus automatically adapt to Blowfish theme’s dark mode toggle and language switching.

Complete Dynamic Configuration
#

Full code for layouts/partials/comments.html:

<!-- Giscus Comments with Dynamic Theme and Language -->
{{ $lang := .Site.Language.Lang }}
{{ $translationKey := .File.TranslationBaseName }}
<script>
  (function() {
    // Get current theme (dark/light)
    function getGiscusTheme() {
      const isDark = document.documentElement.classList.contains('dark');
      return isDark ? 'dark_tritanopia' : 'light_tritanopia';
    }

    // Get language from Hugo template
    const currentLang = '{{ $lang }}';

    // Use file directory path for unified comments across languages
    // Example: "posts/subscription_alert" for both index.ko.md and index.en.md
    const discussionId = '{{ .File.Dir | replaceRE "^content/" "" | replaceRE "/$" "" }}';

    // Wait for DOM to be ready
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initGiscus);
    } else {
      initGiscus();
    }

    function initGiscus() {
      // Create and insert Giscus script with dynamic settings
      const script = document.createElement('script');
      script.src = 'https://giscus.app/client.js';
      script.setAttribute('data-repo', '0AndWild/0AndWild.github.io');
      script.setAttribute('data-repo-id', 'R_kgDOQAqZFA');
      script.setAttribute('data-category', 'General');
      script.setAttribute('data-category-id', 'DIC_kwDOQAqZFM4CwwRg');
      script.setAttribute('data-mapping', 'specific');
      script.setAttribute('data-term', discussionId);
      script.setAttribute('data-strict', '0');
      script.setAttribute('data-reactions-enabled', '1');
      script.setAttribute('data-emit-metadata', '0');
      script.setAttribute('data-input-position', 'bottom');
      script.setAttribute('data-theme', getGiscusTheme());
      script.setAttribute('data-lang', currentLang);
      script.setAttribute('data-loading', 'lazy');
      script.setAttribute('crossorigin', 'anonymous');
      script.async = true;

      // Find giscus container or create one
      const container = document.querySelector('.giscus-container') || document.currentScript?.parentElement;
      if (container) {
        container.appendChild(script);
      }
    }

    // Monitor theme changes and update Giscus
    function updateGiscusTheme() {
      const iframe = document.querySelector('iframe.giscus-frame');
      if (!iframe) return;

      const theme = getGiscusTheme();

      try {
        iframe.contentWindow.postMessage(
          {
            giscus: {
              setConfig: {
                theme: theme
              }
            }
          },
          'https://giscus.app'
        );
      } catch (error) {
        console.log('Giscus theme update delayed, will retry...');
      }
    }

    // Watch for theme changes using MutationObserver
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.attributeName === 'class') {
          // Delay update to ensure iframe is ready
          setTimeout(updateGiscusTheme, 100);
        }
      });
    });

    // Start observing after a short delay
    setTimeout(() => {
      observer.observe(document.documentElement, {
        attributes: true,
        attributeFilter: ['class']
      });
    }, 500);

    // Update theme when Giscus iframe loads
    window.addEventListener('message', (event) => {
      if (event.origin !== 'https://giscus.app') return;
      if (event.data.giscus) {
        // Giscus is ready, update theme
        setTimeout(updateGiscusTheme, 200);
      }
    });
  })();
</script>

<style>
  /* Ensure Giscus iframe has proper height and displays all content */
  .giscus-container {
    min-height: 300px;
  }

  .giscus-container iframe.giscus-frame {
    width: 100%;
    border: none;
    min-height: 300px;
  }

  /* Make sure comment actions are visible */
  .giscus {
    overflow: visible !important;
  }
</style>

<div class="giscus-container"></div>

How It Works
#

1. Dynamic Language Setting
#
{{ $lang := .Site.Language.Lang }}
const currentLang = '{{ $lang }}';
  • Gets current page language from Hugo template
  • Korean page: ko, English page: en
  • Sets Giscus to the corresponding language

Result:

  • Korean page β†’ Giscus UI displays in Korean
  • English page β†’ Giscus UI displays in English
  • Language switch triggers page reload with automatic update
2. Dynamic Dark Mode Setting
#
function getGiscusTheme() {
  const isDark = document.documentElement.classList.contains('dark');
  return isDark ? 'dark_tritanopia' : 'light_tritanopia';
}
  • Blowfish theme adds <html class="dark"> in dark mode
  • Detects this to determine theme
  • Uses dark_tritanopia / light_tritanopia themes (colorblind-friendly)

Result:

  • Page load: Loads Giscus with current theme state
  • Dark mode toggle click: Real-time Giscus theme change
3. Unified Comments Across Languages
#
const discussionId = '{{ .File.Dir | replaceRE "^content/" "" | replaceRE "/$" "" }}';
  • Uses file directory path as Discussion ID
  • content/posts/subscription_alert/index.ko.md β†’ posts/subscription_alert
  • content/posts/subscription_alert/index.en.md β†’ posts/subscription_alert
  • Same ID means Korean/English versions share the same comments

Result:

  • Comments written on Korean post
  • Also display on English post
  • Separate Discussions created per post
4. Real-time Theme Change Detection
#
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.attributeName === 'class') {
      setTimeout(updateGiscusTheme, 100);
    }
  });
});
  • MutationObserver detects HTML class changes
  • Immediately detects dark mode toggle clicks
  • Sends theme change command to Giscus iframe via postMessage

Testing Method
#

# 1. Run local server
hugo server -D

# 2. Verify in browser
http://localhost:1313/posts/subscription_alert/

Test Items:

  1. βœ… Page load displays Giscus with current theme (light/dark)
  2. βœ… Dark mode toggle click immediately changes Giscus theme
  3. βœ… Language switch (ko β†’ en) changes Giscus language
  4. βœ… Korean/English pages display same comments

Changing Theme Options
#

To use different themes, modify the getGiscusTheme() function:

// Basic theme
function getGiscusTheme() {
  const isDark = document.documentElement.classList.contains('dark');
  return isDark ? 'dark' : 'light';
}

// High contrast theme
function getGiscusTheme() {
  const isDark = document.documentElement.classList.contains('dark');
  return isDark ? 'dark_high_contrast' : 'light_high_contrast';
}

// GitHub style theme
function getGiscusTheme() {
  const isDark = document.documentElement.classList.contains('dark');
  return isDark ? 'dark_dimmed' : 'light';
}

Available themes:

  • light / dark
  • light_high_contrast / dark_high_contrast
  • light_tritanopia / dark_tritanopia (colorblind-friendly)
  • dark_dimmed
  • transparent_dark
  • preferred_color_scheme (follows system settings)

Static Theme Configuration (Simple Method)
#

If dynamic changes aren’t needed, you can configure statically:

<script src="https://giscus.app/client.js"
        data-repo="0AndWild/0AndWild.github.io"
        data-repo-id="R_kgDOxxxxxxxx"
        data-category="General"
        data-category-id="DIC_kwDOxxxxxxxx"
        data-mapping="pathname"
        data-theme="preferred_color_scheme"
        data-lang="en"
        crossorigin="anonymous"
        async>
</script>

Pros: Simple Cons: No real-time theme changes, comments separated by language

Hide Comments on Specific Posts
#

To hide comments on specific posts only, add to that post’s front matter:

---
title: "Post Without Comments"
showComments: false  # Hide comments on this post only
---

Separate Comments by Category
#

To use different Discussion categories for posts in different categories:

<!-- Conditional category configuration -->
<script>
  const category = {{ if in .Params.categories "Tutorial" }}
    "DIC_kwDOxxxxTutorial"
  {{ else }}
    "DIC_kwDOxxxxGeneral"
  {{ end }};
</script>

<script src="https://giscus.app/client.js"
        ...
        data-category-id="{{ category }}"
        ...>
</script>

Troubleshooting
#

Comment Widget Not Displaying
#

Cause 1: Discussions Not Enabled
#

Solution: GitHub repository β†’ Settings β†’ Check Discussions

Cause 2: Giscus App Not Installed
#

Solution: Install at https://github.com/apps/giscus

Cause 3: Repository ID Error
#

Solution: Regenerate code at giscus.app

Cause 4: showComments Setting Missing
#

# config/_default/params.toml
[article]
  showComments = true  # Verify

Only Login Button Shows, Can’t Comment
#

Cause: GitHub OAuth Authorization Needed
#

1. Click "Sign in with GitHub"
2. Authorize OAuth permissions
3. Redirect to repository
4. Can write comments

Comments Not Saving
#

Cause: Repository Permission Issue
#

Check:
1. Is the repository Public?
2. Is the repository included in Giscus App permissions?
3. Does the Discussion category exist?

Dark Mode Not Syncing
#

Solution: Add JavaScript Sync Code
#

Refer to “Advanced Configuration > Automatic Dark Mode Switching” above


Managing Giscus
#

Comment Management
#

Manage via GitHub Discussions
#

1. GitHub repository β†’ Discussions tab
2. Click the relevant Discussion
3. Management actions:
   - Edit comment (own comments only)
   - Delete comment (admin)
   - Block user (admin)
   - Lock Discussion (admin)

Handling Spam Comments
#

1. Find spam comment in GitHub Discussions
2. ... menu next to comment β†’ "Delete"
3. Block user: Profile β†’ Block user

Notification Settings
#

Receive Comment Notifications via GitHub
#

1. GitHub β†’ Settings β†’ Notifications
2. Add repository to Watching
3. Configure email notifications

Receive Notifications for Specific Discussions Only
#

1. Discussions tab β†’ Relevant Discussion
2. "Subscribe" button on right
3. Select "Notify me"

Statistics and Analytics
#

View Comment Statistics
#

In GitHub Discussions:

1. Discussions tab
2. Check number of Discussions by category
3. Check comment count for each Discussion

Utilize GitHub Insights
#

GitHub repository β†’ Insights β†’ Community
β†’ Check Discussions activity

Cost and Limitations
#

Cost
#

Completely Free

  • Only need a GitHub account
  • Unlimited comments within repository size limits

Limitations
#

GitHub API Rate Limit
#

  • 60 requests/hour (unauthenticated)
  • 5,000 requests/hour (authenticated)
  • Giscus is optimized with caching, so no issues

Repository Size
#

  • GitHub Free: 1GB per repository
  • Text comments alone won’t reach the limit

Discussions Limit
#

  • None (unlimited)

Alternative Comparisons
#

Giscus vs Utterances
#

ItemGiscusUtterances
BackendDiscussionsIssues
Reactionsβœ…βŒ
Nested RepliesNested supportFlat
Recommendation⭐⭐⭐⭐⭐⭐⭐⭐

Conclusion: Giscus is recommended

Giscus vs Disqus
#

ItemGiscusDisqus
CostFreeFree (with ads)
AdsβŒβœ…
Anonymous CommentsβŒβœ… (Guest)
Markdownβœ…βš οΈ
Data Ownershipβœ…βŒ
RecommendationDeveloper blogsGeneral blogs

Migration Guide
#

Utterances β†’ Giscus
#

1. Convert GitHub Issues to Discussions
   - Manual work required (no automation)
   - Or leave Issues as-is and start fresh with Giscus

2. Replace comments.html file
   - Delete Utterances code
   - Add Giscus code

3. Deploy

Disqus β†’ Giscus
#

1. Export Disqus data (XML)
2. Manual migration to GitHub Discussions
   - No automation tools available
   - Need to write custom script
   - Or starting fresh recommended

Additional Resources
#

Official Documentation
#

Community
#


Checklist
#

Installation completion checklist:

  • GitHub Discussions enabled
  • Giscus App installed
  • Created layouts/partials/comments.html
  • Inserted Giscus code (with your own IDs)
  • Set showComments = true in params.toml
  • Local testing complete
  • Pushed to GitHub
  • Verified on deployed site
  • Wrote test comment
  • Verified creation in GitHub Discussions

Conclusion
#

Giscus is the most suitable comment system for Hugo/GitHub Pages blogs:

Summary of Advantages
#

βœ… Completely free βœ… Simple setup (10 minutes) βœ… No server required βœ… Full Markdown support βœ… GitHub integration βœ… Data ownership

Disadvantages
#

❌ GitHub account required (no anonymous comments) ❌ Best for technical blogs (barrier for general users)

Recommended For#

  • βœ… Developer blogs
  • βœ… Technical documentation
  • βœ… Open source projects
Hugo - This article is part of a series.
Part 3: This Article

Related