Back to Blog

How to Integrate Giscus Comments in Next.js: A Complete Guide

How to Integrate Giscus Comments in Next.js: A Complete Guide

If you're building a blog with Next.js and looking for a modern, privacy-friendly comment system, Giscus is an excellent choice. Built on GitHub Discussions, it's free, open-source, and doesn't track your users. In this guide, I'll show you exactly how I integrated Giscus into my portfolio blog.

Why Giscus?

Before diving into the implementation, let's understand why Giscus stands out:

GitHub-powered

Uses GitHub Discussions as the backend

Zero tracking

No ads, no tracking, completely privacy-friendly

Free forever

Built on GitHub's free tier

Markdown support

Full Markdown and code syntax highlighting

Reactions

GitHub-style reactions for comments

Theme support

Matches your site's light/dark mode

SEO-friendly

Comments are indexed by search engines

Prerequisites

Before starting, make sure you have:

  • A Next.js project (App Router or Pages Router)
  • A GitHub repository for your project
  • GitHub Discussions enabled on your repository

Step 1: Enable GitHub Discussions

First, enable Discussions on your repository:

  1. Go to your repository on GitHub
  2. Click on Settings
  3. Scroll down to the Features section
  4. Check the Discussions checkbox
  5. Click Set up discussions
  6. Create an initial discussion post (optional but recommended)

Step 2: Install the Giscus GitHub App

Install the Giscus app to allow it to interact with your repository:

  1. Visit github.com/apps/giscus
  2. Click Install
  3. Select your repository
  4. Grant the necessary permissions

Step 3: Get Your Giscus Configuration

Now, get your unique configuration values:

  1. Visit giscus.app
  2. Enter your repository in the format: username/repo-name
  3. Select your preferred category (I recommend "General" or "Announcements")
  4. Choose your mapping (pathname is recommended)
  5. Copy the generated data-repo-id and data-category-id values

Step 4: Create the Giscus Component

Create a reusable Giscus component with theme support:

// src/components/giscus.tsx
"use client";
 
import { useEffect, useRef } from "react";
import { useTheme } from "next-themes";
 
interface GiscusProps {
  repo: string;
  repoId: string;
  category: string;
  categoryId: string;
  mapping?: string;
  reactionsEnabled?: string;
  emitMetadata?: string;
  inputPosition?: "top" | "bottom";
  lang?: string;
  loading?: "lazy" | "eager";
}
 
export function Giscus({
  repo,
  repoId,
  category,
  categoryId,
  mapping = "pathname",
  reactionsEnabled = "1",
  emitMetadata = "0",
  inputPosition = "bottom",
  lang = "en",
  loading = "lazy",
}: GiscusProps) {
  const ref = useRef<HTMLDivElement>(null);
  const { resolvedTheme } = useTheme();
 
  const theme = resolvedTheme === "dark" ? "dark" : "light";
 
  useEffect(() => {
    if (!ref.current || ref.current.hasChildNodes()) return;
 
    const script = document.createElement("script");
    script.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", mapping);
    script.setAttribute("data-strict", "0");
    script.setAttribute("data-reactions-enabled", reactionsEnabled);
    script.setAttribute("data-emit-metadata", emitMetadata);
    script.setAttribute("data-input-position", inputPosition);
    script.setAttribute("data-theme", theme);
    script.setAttribute("data-lang", lang);
    script.setAttribute("data-loading", loading);
    script.crossOrigin = "anonymous";
    script.async = true;
 
    ref.current.appendChild(script);
  }, [
    repo,
    repoId,
    category,
    categoryId,
    mapping,
    reactionsEnabled,
    emitMetadata,
    inputPosition,
    lang,
    loading,
    theme,
  ]);
 
  // Update theme dynamically when user switches
  useEffect(() => {
    const iframe = document.querySelector<HTMLIFrameElement>(
      "iframe.giscus-frame",
    );
    if (!iframe) return;
 
    iframe.contentWindow?.postMessage(
      { giscus: { setConfig: { theme } } },
      "https://giscus.app",
    );
  }, [theme]);
 
  return <div ref={ref} />;
}

Key Features of This Component:

  • Client-side rendering: Uses "use client" directive
  • Theme synchronization: Automatically matches your site's theme
  • Dynamic theme updates: Updates when users switch themes
  • TypeScript support: Fully typed for better DX

Step 5: Store Configuration

Create a configuration file to centralize your Giscus settings:

// src/config/config.json
{
  "giscus": {
    "enable": true,
    "repo": "username/repo-name",
    "repoId": "YOUR_REPO_ID",
    "category": "General",
    "categoryId": "YOUR_CATEGORY_ID",
    "mapping": "pathname",
    "reactionsEnabled": "1",
    "emitMetadata": "0",
    "inputPosition": "bottom",
    "lang": "en",
    "loading": "lazy"
  }
}

Replace YOUR_REPO_ID and YOUR_CATEGORY_ID with the values from giscus.app.

Step 6: Add to Your Blog Posts

Now integrate Giscus into your blog post template:

// src/app/blog/[slug]/page.tsx
import { Giscus } from "@/components/giscus";
import config from "@/config/config.json";
 
export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  // ... your existing code
 
  return (
    <article>
      {/* Your blog content */}
 
      {/* Comments Section */}
      {config.giscus.enable && (
        <div className="mt-12 md:mt-16 pt-8 border-t border-border">
          <h2 className="text-2xl font-bold mb-6">Comments</h2>
          <Giscus
            repo={config.giscus.repo}
            repoId={config.giscus.repoId}
            category={config.giscus.category}
            categoryId={config.giscus.categoryId}
            mapping={config.giscus.mapping}
            reactionsEnabled={config.giscus.reactionsEnabled}
            emitMetadata={config.giscus.emitMetadata}
            inputPosition={config.giscus.inputPosition as "top" | "bottom"}
            lang={config.giscus.lang}
            loading={config.giscus.loading as "lazy" | "eager"}
          />
        </div>
      )}
    </article>
  );
}

Step 7: Add Styling (Optional)

Add some custom styling to make the comments section blend with your design:

/* Global styles or component CSS */
.giscus-frame {
  width: 100%;
  border: none;
  color-scheme: auto;
}
 
/* Adjust spacing */
.giscus {
  margin-top: 2rem;
}

Configuration Options Explained

Here’s what each configuration option means:

mapping
Determines how comments are mapped to pages.

  • pathname — Based on URL path (recommended for most blogs)
  • url — Based on full URL
  • title — Based on page title
  • og:title — Based on Open Graph title

inputPosition
Controls where the comment input box appears.

  • bottom — Below existing comments (default)
  • top — Above existing comments

reactionsEnabled
Enables or disables GitHub reactions (👍, 😄, etc.).

loading
Controls when comments are loaded.

  • lazy — Load when scrolled into view (better performance)
  • eager — Load immediately

Best Practices

1. Use Lazy Loading

Set loading: "lazy" to improve initial page load performance. Comments will load when users scroll to them.

2. Choose the Right Mapping

For blogs, pathname mapping is ideal. It keeps comments consistent even if you change page titles or URLs.

3. Enable Reactions

Reactions encourage engagement without requiring full comments. Enable them with reactionsEnabled: "1".

4. Create Discussion Categories

Organize discussions by creating specific categories:

  • "Blog Comments" for blog posts
  • "General" for site-wide discussions
  • "Q&A" for questions

5. Moderate Effectively

As the repository owner, you can:

  • Edit or delete comments
  • Lock discussions to prevent new comments
  • Mark comments as answers (in Q&A categories)
  • Hide abusive content

Troubleshooting

Solution: Install the Giscus GitHub app at github.com/apps/giscus and grant it access to your repository.

Checklist:

  • ✅ GitHub Discussions is enabled
  • ✅ Giscus app is installed
  • ✅ Repository is public
  • repoId and categoryId are correct

Solution: Ensure your Giscus component is a client component ("use client") and uses useTheme() from next-themes.

Solution: Use pathname mapping instead of title or url. Check that your paths are unique.

Advanced: Custom Themes

You can create custom Giscus themes to match your brand:

  1. Fork the giscus-component repository
  2. Create a new CSS theme file
  3. Submit a PR or use it directly with data-theme attribute

For now, the built-in light and dark themes work great for most sites.

Performance Considerations

Giscus is lightweight, but here are tips to optimize further:

  1. Use lazy loading: Defers loading until needed
  2. Defer non-critical scripts: Load Giscus after main content
  3. Monitor bundle size: Keep your component minimal
  4. Cache GitHub API responses: GitHub handles this for you

Conclusion

Giscus is an excellent choice for adding comments to your Next.js blog. It's:

  • Free and privacy-friendly
  • Easy to integrate
  • Powerful with GitHub's features
  • Theme-aware and accessible

The best part? Your readers can comment using their GitHub accounts, and all discussions are stored in your repository's Discussions tab.

Have questions about this integration? Drop a comment below using the Giscus system we just set up! 🎉

Resources


Found this tutorial helpful? Share it with others who might benefit!

Comments