ehsan.blog
~/blog/how-to-add-giscus-comments-to-astro — zsh
cat how-to-add-giscus-comments-to-astro.md

How to add Giscus (GitHub Discussions) comments to an Astro blog

·5 min read

A static blog has no server, so the usual answer to “how do I add comments?” is “sign up for a hosted comment service.” Giscus is a nicer answer: it’s a free, open-source widget that stores every comment as a GitHub Discussion on a repo you own. No database, no backend, no accounts to manage — your readers sign in with GitHub, and the comments live in your repo.

This is exactly how comments work on this blog. Let me walk through how it’s wired up, end to end.

What Giscus actually is

Giscus is a <script> you drop onto a page. When it loads, it renders an <iframe> that talks to the GitHub Discussions API. Each blog post maps to one Discussion; each reply is a comment on that Discussion. Because everything is stored on GitHub, you get moderation, reactions, and Markdown for free, and you never store user data yourself.

The one-time setup happens on GitHub and on the giscus site — the code in your repo just points at the result.

Step 1: Prepare the GitHub repo

Three things have to be true on the repo that will hold the comments:

  1. The repo is public.
  2. Discussions are enabled (repo Settings → Features → Discussions).
  3. The giscus GitHub App is installed on that repo (from github.com/apps/giscus).

Step 2: Get your repo-id and category-id

Go to giscus.app, type in your owner/repo, and pick a Discussions category (a general one like “Announcements” works well). The page generates a config snippet for you. The two values you must copy out are:

  • data-repo-id — a stable id for the repo
  • data-category-id — a stable id for the category

These are not secrets, but they are the two pieces the widget can’t guess on its own.

Step 3: Put the config in one place

This blog keeps all the Giscus values in a single typed constant, so the component that renders the widget stays clean. Here’s the real config:

ts
// blog/src/consts.ts
export const GISCUS = {
  repo: "developerehsan/developerEhsan-portfolio", // owner/repo
  repoId: "REPLACE_ME_FROM_GISCUS_APP", // data-repo-id from giscus.app
  category: "Announcements", // Discussions category name
  categoryId: "REPLACE_ME_FROM_GISCUS_APP", // data-category-id from giscus.app
  mapping: "pathname",
  theme: "dark_dimmed",
} as const

A couple of fields worth explaining:

  • mapping: "pathname" tells Giscus how to decide which Discussion belongs to which page. "pathname" means “one Discussion per URL path” — so /posts/my-post gets its own thread. This is the option you usually want.
  • theme is a Giscus theme name. "dark_dimmed" matches this blog’s dark terminal look. More on themes below.

The REPLACE_ME_FROM_GISCUS_APP placeholders are the signal that setup isn’t finished yet. That matters for the next step.

Step 4: The component

Here’s the actual Giscus.astro component. Read it top to bottom — it’s short.

astro
---
// blog/src/components/Giscus.astro
import { GISCUS } from "@/consts"

const configured =
  GISCUS.repoId !== "REPLACE_ME_FROM_GISCUS_APP" &&
  GISCUS.categoryId !== "REPLACE_ME_FROM_GISCUS_APP"
---

<section aria-label="Comments" class="mt-12">
  <div class="mb-4 flex items-center gap-2 font-mono text-xs text-[oklch(0.6_0_0)]">
    <span class="font-bold text-[oklch(0.6_0.1_150)]">❯</span>
    <span>cat ./comments</span>
  </div>
  {
    configured ? (
      <div class="giscus"></div>
    ) : (
      <p class="terminal-card p-4 font-mono text-xs text-[oklch(0.62_0_0)]">
        Comments are not configured yet. Enable GitHub Discussions and paste the
        giscus repo-id / category-id into <code>src/consts.ts</code>.
      </p>
    )
  }
</section>

The configured check is the nice part. If the ids are still placeholders, the component renders a friendly note instead of a broken widget — no error, no half-loaded iframe. Once you paste real ids into consts.ts, configured flips to true and the real container renders.

Step 5: The script embed

The <div class="giscus"> above is just a mounting point. The widget is actually loaded by Giscus’s client script, which this blog renders only when configured is true:

astro
{
  configured && (
    <script
      is:inline
      src="https://giscus.app/client.js"
      data-repo={GISCUS.repo}
      data-repo-id={GISCUS.repoId}
      data-category={GISCUS.category}
      data-category-id={GISCUS.categoryId}
      data-mapping={GISCUS.mapping}
      data-strict="0"
      data-reactions-enabled="1"
      data-emit-metadata="0"
      data-input-position="top"
      data-theme={GISCUS.theme}
      data-lang="en"
      data-loading="lazy"
      crossorigin="anonymous"
      async
    />
  )
}

The important attributes here:

  • is:inline is an Astro instruction. Normally Astro processes and bundles <script> tags; is:inline tells it to leave this one exactly as written so the Giscus loader runs as-is with all its data-* attributes intact.
  • data-loading="lazy" defers loading the iframe until it’s near the viewport, so comments don’t slow down the initial page.
  • data-reactions-enabled="1" shows the 👍/❤️ reaction bar.
  • data-input-position="top" puts the comment box above the thread.

Every data-* value comes straight from the GISCUS object, so the widget and your config can never drift apart.

Matching the dark theme

The blog is dark-only, so a bright default comment box would stick out. Two things keep Giscus on-brand:

  1. The theme: "dark_dimmed" value in consts.ts — Giscus ships several built-in dark themes, and this one has muted, low-contrast surfaces that fit the blog’s OKLCH terminal palette. If you want a perfect match you can also point data-theme at a full CSS URL, but a built-in dark theme is the easy win.
  2. The wrapper markup uses the blog’s own tokens (terminal-card, the mono font, the ❯ cat ./comments prompt) so the section around the widget reads like the rest of the site.

What to remember

  • Giscus turns GitHub Discussions into a comment section — no backend, no database, and comments live in a repo you control.
  • The one-time setup is on GitHub (public repo + Discussions + the giscus app) and on giscus.app (copy the repo-id and category-id).
  • Keep the config in one typed constant and gate rendering on whether the ids are still placeholders, so an unconfigured widget degrades gracefully.
  • Use is:inline so Astro doesn’t rewrite the Giscus loader script.
  • Match your site by picking a built-in dark theme like dark_dimmed.

Related reading: comments are one of a few small client-side add-ons on this blog — see also privacy-friendly analytics with a tiny Astro component.

ls ./related
cat ./comments

Comments are not configured yet. Enable GitHub Discussions and paste the giscus repo-id / category-id into src/consts.ts.