Fuwari is an Astro-based static blog template that developers like for its clean design and fast output. Comments are the usual weak spot. A conventional comment system often brings a backend and a database, which works against the simplicity of a static site. Giscus takes a different route: it keeps discussions in GitHub Discussions, needs no database, and can follow the page's light or dark theme. This walkthrough adds that setup to Fuwari.

Prepare a public GitHub repository for comments

Giscus reads and writes comments through GitHub Discussions, so start with a public repository dedicated to that purpose. The repository must be public because Giscus accesses it when loading and submitting comments.

  1. Create the repository - Sign in to GitHub and open the + menu in the upper-right corner. - Choose New repository. - Give it a name such as fuwari-comments and fill in any other required details. - Set visibility to Public.

  2. Enable Discussions - Open the repository's Settings → Features. - Find Discussions, check it, and click Save changes. - Discussions is GitHub's discussion area for projects; Giscus uses it as the comment store.

Configure Giscus

Next, connect Giscus to the repository.

  1. Connect the repository - Go to https://giscus.app/zh-CN. - Pick a language, then enter your repository in the field below. - Once the check passes, the Discussion connection is tied to that repository.

Giscus repository configuration

  1. Choose a mapping pathname is a good default. If a post lives at posts/helloworld, keeping that path unchanged keeps the same discussion matched to the page.

Giscus mapping configuration

  1. Choose a Discussion category The Announcements category is recommended. It keeps new discussions creatable only by repository maintainers and Giscus.

Giscus category configuration

  1. Save the configuration values After these steps, note the following for Fuwari: - repository name - repository ID - category name - category ID

Add Giscus to Fuwari

  1. Create the Giscus component

To make Giscus render correctly in both light and dark mode, create src/components/misc/Giscus.astro with the following content:

---
interface Props {
  repo: string;
  repoId: string;
  category: string;
  categoryId: string;
  mapping?: string;
  reactionsEnabled?: boolean;
  emitMetadata?: boolean;
  inputPosition?: 'top' | 'bottom';
  lang?: string;
}

const {
  repo,
  repoId,
  category,
  categoryId,
  mapping = 'pathname',
  reactionsEnabled = true,
  emitMetadata = false,
  inputPosition = 'bottom',
  lang = 'zh-CN'
} = Astro.props;
---

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

<script define:vars={{ repo, repoId, category, categoryId, mapping, reactionsEnabled, emitMetadata, inputPosition, lang }}>
function loadGiscus() {
  const container = document.getElementById('giscus-container');
  if (!container) return;

  const isDark = document.documentElement.classList.contains('dark');
  const theme = isDark ? 'dark' : 'light';

  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 ? '1' : '0');
  script.setAttribute('data-emit-metadata', emitMetadata ? '1' : '0');
  script.setAttribute('data-input-position', inputPosition);
  script.setAttribute('data-theme', theme);
  script.setAttribute('data-lang', lang);
  script.setAttribute('data-loading', 'lazy');
  script.crossOrigin = 'anonymous';
  script.async = true;

  container.appendChild(script);
}

// Watch for theme changes
function updateGiscusTheme() {
  const giscusFrame = document.querySelector('iframe[src*="giscus"]');
  if (giscusFrame) {
    const isDark = document.documentElement.classList.contains('dark');
    const theme = isDark ? 'dark' : 'light';

    giscusFrame.contentWindow.postMessage({
      giscus: {
        setConfig: {
          theme: theme
        }
      }
    }, 'https://giscus.app');
  }
}

// Observe DOM changes to detect theme switches
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
      updateGiscusTheme();
    }
  });
});

// Initialize on page load
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', loadGiscus);
} else {
  loadGiscus();
}

// Start observing theme changes
observer.observe(document.documentElement, {
  attributes: true,
  attributeFilter: ['class']
});
</script>
  1. Insert the component into post pages

In src/pages/posts/[...slug].astro, first import the component:

import path from "node:path";
import License from "@components/misc/License.astro";
import Markdown from "@components/misc/Markdown.astro";
import AISummary from "@components/misc/AISummary.astro";
import Giscus from "../../components/misc/Giscus.astro";
import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation";
import MainGridLayout from "@layouts/MainGridLayout.astro";
import { getSortedPosts } from "@utils/content-utils";
import { getDir, getPostUrlById } from "@utils/url-utils";
import { Icon } from "astro-icon/components";
import { licenseConfig } from "src/config";
import ImageWrapper from "../../components/misc/ImageWrapper.astro";
import PostMetadata from "../../components/PostMeta.astro";
import { profileConfig, siteConfig } from "../../config";
import { formatDateToYYYYMMDD } from "../../utils/date-utils";

Then place the Giscus component after the license component:

<!-- always show cover as long as it has one -->

{entry.data.image &&
<ImageWrapper id="post-cover" src={entry.data.image} basePath={path.join("content/posts/", getDir(entry.id))} class="mb-8 rounded-xl banner-container onload-animation"/>
}

<Markdown class="mb-6 markdown-content onload-animation">
<Content />
</Markdown>

{licenseConfig.enable && <License title={entry.data.title} slug={entry.slug} pubDate={entry.data.published} class="mb-6 rounded-xl license-container onload-animation"></License>}

<Giscus
repo="username/repo"
repoId=""
category="Announcements"
categoryId=""
/>
<br>
</div>
</div>

After saving, rebuild the project and the comment area should appear.

With those steps, Fuwari has a working comment system backed by GitHub Discussions. 🎉