How Hugo and Oink Integrate Giscus Comments
Hugo produces static HTML. It does not provide a server that accepts comments, a database, or a user system. Adding comments to a Hugo site with Oink therefore means delegating the comment interface to Giscus and storing the data in GitHub Discussions.
When I added comments to the site, I wanted one conversation for everyone—whether they opened the Chinese or English page, on Vercel or GitHub Pages. Here’s how the pieces work together to bring a comment onto a static blog page.

What the four components do
| Component | Responsibility |
|---|---|
| Hugo | Reads Markdown and configuration and generates blog pages |
| Oink | Provides page templates and decides when and where to output a comment container |
| Giscus | Displays the comment interface and communicates with GitHub |
| GitHub Discussions | Stores discussions, replies, identities, and reactions |
The relationship is:
Comment data always remains in GitHub Discussions. The blog embeds an entry point for viewing and posting Discussion content. The official Giscus documentation explains that Giscus uses the GitHub Discussions search API to find the discussion mapped to a page and creates one when a visitor first comments or reacts if none exists.
Why a static site needs an external comment backend
A conventional comment system needs at least this flow:
That normally means maintaining an application server, database, login system, permissions, and spam controls. Hugo only builds static files; it does not continuously run an application that handles those requests.
Giscus reuses GitHub’s existing services:
- GitHub accounts provide identity.
- GitHub Discussions store the main post, replies, and reactions.
- Giscus maps the current page to one Discussion and provides the embedded interface.
- Repository maintainers moderate, reply, pin, lock, or remove content in Discussions.
The site therefore needs neither a comment database nor a GitHub token in hugo.yml.
Why a Hugo parameter can control comments
Hugo lets a site define parameters in configuration or page front matter. For example:
To Hugo, this is simply a value. Oink’s templates give it meaning by reading site-level and page-level comment parameters and deciding whether to output the comment container and load Giscus.
Hugo’s front matter documentation explains that metadata at the top of a page can describe content and influence template selection and publication structure. This site keeps the full Giscus configuration in hugo.yml and uses section cascades to control where it appears:
- Blog articles inherit the global comment setting.
- The blog index explicitly sets
comments: false. - The home, learning, and experience sections do not display comments.
- Chinese and English versions of a blog post use the same discussion identifier.
This avoids repeating the entire Giscus configuration in every article.
What the Giscus configuration contains
The central configuration for this site is:
The four essential identifiers are:
| Setting | Purpose |
|---|---|
repo | Selects the public repository containing Discussions |
repoId | GitHub’s public unique identifier for that repository |
category | Selects the category for new discussions |
categoryId | GitHub’s public unique identifier for that category |
These are routing identifiers, not secrets or access tokens. The target repository must also be public, have Discussions enabled, and have the Giscus App installed. The official configurator recommends an Announcements-type category so only maintainers and Giscus can create new Discussions there.
The remaining settings control behavior:
reactionsEnabled: 1displays reactions on the main discussion post.emitMetadata: 0disables periodic Discussion metadata messages to the parent page.inputPosition: bottomputs the input box below existing comments.loading: lazydelays loading until the iframe is near the viewport.theme: autofollows the site’s light or dark mode.- Chinese pages use
zh-CN, while English pages useen. This changes the Giscus interface, not the language of user comments.
How the page loads comments
The process has a build phase and a browser phase.
Build phase
When Hugo builds a blog page, Oink checks the comment switch and required configuration. If the conditions pass, the template outputs a parameterized container similar to:
No comment data exists in the generated HTML. It only reserves a container and tells the browser which repository, category, and discussion identifier to use later.
Browser phase
After a visitor opens the page, Oink’s JavaScript dynamically loads:
Giscus then creates an iframe. It appears at the bottom of the blog, but technically comes from an independent page served by giscus.app:
The iframe isolates the comment application from the blog page. Oink also watches the site’s color mode and uses browser postMessage events to send theme changes to the Giscus iframe.
If the script or iframe fails to load, Oink exits the loading state and displays a localized error message instead of leaving the page waiting indefinitely.
How one article maps to one Discussion
Giscus needs to know which Discussion belongs to the current page. Available mappings include the full URL, pathname, the page title, and a specific string.
A single-language site on one domain can often use:
Different paths then normally create different discussions. This site, however, has Chinese and English content on two deployments:
Using the browser pathname directly would split one article across several Discussions. The site therefore uses:
A Hugo template derives a common data-term from .Page.Path:
Hugo’s Page.Path documentation states that the logical path excludes file extensions and language identifiers. Removing deployment domains and the GitHub Pages base path gives all four entry points the same identifier:
The title may be translated and the domain may change while the comment thread remains stable. Changing the article slug changes this identifier, so a slug change should preserve the old term or include a deliberate Discussion migration plan.
How comments are created and moderated
After loading, Giscus searches the configured repository and category with the mapping term:
- If a match exists, it fetches and displays the existing comments.
- If no match exists, it initially displays an empty comment section.
- The first comment or reaction causes Giscus Bot to create the Discussion.
- A visitor authorizes Giscus through GitHub OAuth to post on their behalf.
- The site owner moderates content in GitHub Discussions.
Visitors may also participate directly in the GitHub Discussion. Both interfaces operate on the same data.
Summary
Giscus does not copy comments into Hugo. It embeds a GitHub Discussion in the blog: Hugo generates the page, Oink decides whether to load comments and passes the configuration, Giscus provides the interface and communication layer, and GitHub Discussions persists identities and data.
For a simple site, pathname may be enough. For a bilingual site with two deployments, the important design choice is a stable identifier independent of language and domain. This site uses specific + /blog/<slug>/ so every version of the same article shares one comment thread.