- Published on
DevBlog 3. Adding Comments with giscus
- Authors

- Name
- seren-wib
Contents
How the comment box at the bottom of each post was added. This blog started from the tailwind-nextjs-starter-blog template, and the comment code was there from day one. Only the settings were missing.
1. Why giscus
| Option | Stored in | Downside |
|---|---|---|
| Disqus | Disqus servers | Comes with ads and tracking scripts. Data isn't yours |
| utterances | GitHub Issues | Comments mix into the issue list. No threaded replies |
| giscus | GitHub Discussions | Visitors need a GitHub account to comment |
- A static blog with no server or database has nowhere to store comments. GitHub Discussions is borrowed as the store
- Most readers are developers, so requiring a GitHub login isn't a problem
- One post = one discussion. The giscus app creates it when the first comment is posted
2. What the template already had
data/siteMetadata.jshasprovider: 'giscus'and the config fields. Every value is read from environment variables
data/siteMetadata.js
comments: {
provider: 'giscus',
giscusConfig: {
repo: process.env.NEXT_PUBLIC_GISCUS_REPO,
repositoryId: process.env.NEXT_PUBLIC_GISCUS_REPOSITORY_ID,
category: process.env.NEXT_PUBLIC_GISCUS_CATEGORY,
categoryId: process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID,
mapping: 'pathname',
// ...
},
},
- The CSP (
next.config.js) already allowsgiscus.appinscript-srcandframe-src - But all four environment variables were empty, and Discussions was off on the repo. So pressing "Load Comments" showed nothing
3. GitHub setup
Turn on Discussions
- Repo Settings → General → Features → check Discussions
- The default categories (Announcements, General, Q&A, and so on) are created right away
- The "Start discussion" welcome post that follows is optional. It has nothing to do with comments
Install the giscus app
- Install from github.com/apps/giscus
- Under Repository access, pick Only select repositories and choose just the blog repo. All repositories would open private repos to the app too
Get the IDs
- Enter the repo name on giscus.app and it shows the IDs. With
gh, one GraphQL call does it too
gh api graphql -f query='{
repository(owner: "seren-wib", name: "vercel-blog") {
id
discussionCategories(first: 10) { nodes { name id } }
}
}'
idis the repo ID (R_...), and each category'sDIC_...is its category ID
Use the General category
- It started with Announcements, which giscus recommends. Only maintainers and apps can start new discussions there, so visitors can't open stray ones
- But the welcome announcement and the per-post comment discussions ended up mixed together. To keep announcements on their own, comments moved to General
- In General, visitors can also start new discussions from the Discussions tab. An unrelated thread may show up among the comment discussions now and then, which is fine at this scale
- After switching categories, existing discussions have to be moved too. giscus only looks for discussions in the configured category, so an unmoved one hides that post's comments and a new empty discussion gets created. Move them with Change category on the discussion page or the GraphQL
updateDiscussionmutation
4. Vercel environment variables
| Name | Value |
|---|---|
NEXT_PUBLIC_GISCUS_REPO | seren-wib/vercel-blog |
NEXT_PUBLIC_GISCUS_REPOSITORY_ID | R_... |
NEXT_PUBLIC_GISCUS_CATEGORY | General |
NEXT_PUBLIC_GISCUS_CATEGORY_ID | DIC_... |
- None of these are secrets. They end up in the browser anyway, so they're stored as
plain NEXT_PUBLIC_values are baked into the code at build time. Saving the variables alone leaves the site unchanged. It takes one Redeploy to apply them
5. Showing comments without a button
- The template loaded giscus only after clicking "Load Comments", to save page load
- But pliny's giscus component already creates the iframe with
loading: "lazy". It only loads once you scroll near the comments, so dropping the button doesn't change the initial load - So the button and its state were removed and the component renders directly
components/Comments.tsx
export default function Comments({ slug }: { slug: string }) {
if (!siteMetadata.comments?.provider) {
return null
}
return <CommentsComponent commentsConfig={siteMetadata.comments} slug={slug} />
}
- The "Discuss on Twitter" and "View on GitHub" links under each post went too. With a comment box in place, there's no use for them
- On long posts the comments are a long scroll away, but the floating speech-bubble button at the bottom right jumps straight to
#comment
6. Mapping: pathname
mapping: 'pathname'finds the discussion by page path. Discussion titles become paths likekr/blog/devblog/devblog-03-giscus-comments/- This blog serves each post at two addresses,
/kr/and/en/. The paths differ, so Korean and English posts get separate comment threads - Merging them would take the
specificmapping with atermthat drops the language prefix. For now they stay separate, since each language has its own readers - Renaming a slug changes the path and cuts the link to existing comments. If a post's URL ever changes, the discussion title in Discussions has to be updated too
7. Summary
- Comments on a static blog are done without a server by borrowing GitHub Discussions
- The touch points are two places on GitHub (Discussions, app permissions) and four Vercel environment variables
NEXT_PUBLIC_values are build-time values, so a redeploy is required