For a long time I’ve wanted to move away from Disqus to giscus, for a few reasons. Back when I started this blog, Disqus was a convenient way to add comments. But there are plenty of reports about how invasive Disqus is: how much tracking it injects into a page, and how much the platform is disliked in general.
The post that finally pushed me over the edge was Rob J. Hyndman’s Moving from Disqus to giscus. I found it a while ago and it stuck with me.
So here I am, staring at a pile of work to enable giscus for my blog. Let’s go.
This is a multi-step process
Here’s the rough shape of it:
- Enable Discussions in my GitHub repo
- Generate the giscus config values
- Export the Disqus comments
- Fix the URLs in the export
- Import the comments into GitHub Discussions
- Update the Hugo config
- Replace the comments template
- Verify comments show up on the blog
Enable Discussions in my GitHub repo
- Repo Settings → General → Features → check “Discussions”.
- Install the giscus GitHub App: https://github.com/apps/giscus → Configure → grant it to
ingorichter/ingorichter.github.ioonly. GrantRead access to metadataandRead and write access to discussions. This step requires authentication. - The
ingorichter/ingorichter.github.iorepo must be public. Check! - Create a Discussions category for comments:
- Repo Discussions → Categories (pencil icon) → New category.
- Name:
Comments. Type:Announcements(only maintainers open threads — giscus opens them via the app, visitors only reply). Format:Open-ended discussion, but this can’t be selected — it’s the default for this type.
Generate the giscus config values
Go to https://giscus.app and fill in the form:
- Repository:
ingorichter/ingorichter.github.io - Page ↔ Discussions mapping:
pathname - Enable “Use strict title matching”
- Category:
Comments - Enable “Only search for discussions in this category”
- Enable “Load the comments lazily”
- Use the “Preferred color scheme” theme
Copy the generated script. It’s used later in the Hugo comments.html:
1<script src="https://giscus.app/client.js"
2 data-repo="ingorichter/ingorichter.github.io"
3 data-repo-id="MDEwOlJlcG9zaXRvcnkxMDgwNjQ0Mw=="
4 data-category="Comments"
5 data-category-id="DIC_kwDOAKTkq84DEfQE"
6 data-mapping="pathname"
7 data-strict="1"
8 data-reactions-enabled="1"
9 data-emit-metadata="0"
10 data-input-position="top"
11 data-theme="preferred_color_scheme"
12 data-lang="en"
13 data-loading="lazy"
14 crossorigin="anonymous"
15 async>
16</script>Export the Disqus comments
I wanted to keep as many of the Disqus comments as possible. There are only a few, but they matter to me.
- Disqus admin → your site → Community → Export (or
https://<shortname>.disqus.com/admin/discussions/export/). Shortname:ingorichterio. - Wait for the email → download
<shortname>-<date>.xml.gz→gunzipit to.xml.
The email arrived fast.
Fix the URLs in the Disqus export
I asked Claude to help me identify which comments are still live and worth saving. After some back and forth, I had a cleaned-up, minimal XML file to use in the next step.
Import into GitHub Discussions
I used Claude to help me write an importer script that consumes the Disqus export file.
It talks to GitHub through the gh CLI, so it needs the read:discussion and write:discussion scopes on my gh login. I had to run gh auth refresh -s write:discussion,read:discussion before running the script:
1# dry run — already ran, output looked good
2python3 import-to-giscus.py --repo ingorichter/ingorichter.github.io --category Comments --dry-run
3
4# real run
5python3 import-to-giscus.py --repo ingorichter/ingorichter.github.io --category CommentsThe script creates one Discussion per thread (title = pathname) and posts each Disqus comment on it, preserving the author name and date in the body. Replies stay threaded under their parent comment.
I improved the import script until it was idempotent and added an option to wipe all previously imported comments. That helps a lot while testing, right up until everything goes live.
GitHub’s Discussion search index lags a minute or two behind. If you re-run the script right away, it might not see the Discussions it just created. Wait a bit between runs.
Update the Hugo config
Remove the Disqus config from config.toml:
1[services.disqus]
2shortname = 'ingorichterio'Add this block instead. The repoID and categoryID are part of the <script> generated on the giscus site (see above):
1[params.giscus]
2repo = "ingorichter/ingorichter.github.io"
3repoID = "MDEwOlJlcG9zaXRvcnkxMDgwNjQ0Mw=="
4category = "Comments"
5categoryID = "DIC_kwDOAKTkq84DEfQE"
6mapping = "pathname"
7strict = "1"
8reactionsEnabled = "1"
9inputPosition = "top"
10theme = "preferred_color_scheme"Replace the comments template
I needed a new template for Hugo to render the giscus comment section on each post, so I added a new file at layouts/_default/comments.html:
1{{- $g := .Site.Params.giscus -}}
2{{- if and $g $g.repo $g.repoID (ne .Params.comments false) (not .Params.menu) -}}
3 <section class="comments">
4 <h2>Comments</h2>
5 <script src="https://giscus.app/client.js"
6 data-repo="{{ $g.repo }}"
7 data-repo-id="{{ $g.repoID }}"
8 data-category="{{ $g.category }}"
9 data-category-id="{{ $g.categoryID }}"
10 data-mapping="{{ default "pathname" $g.mapping }}"
11 data-strict="{{ default "1" $g.strict }}"
12 data-reactions-enabled="{{ default "1" $g.reactionsEnabled }}"
13 data-emit-metadata="0"
14 data-input-position="{{ default "top" $g.inputPosition }}"
15 data-theme="{{ default "preferred_color_scheme" $g.theme }}"
16 data-lang="en"
17 data-loading="lazy"
18 crossorigin="anonymous"
19 async>
20 </script>
21 <noscript>Enable JavaScript to view comments.</noscript>
22 </section>
23{{- end -}}Now .Render "comments" in single.html picks it up unchanged. Opt out per post with comments: false in the front matter.
Verify comments show up on the blog
Run make preview to launch the local Hugo server.
- Open a post whose thread you test-imported → the giscus box loads and the old comments show.
- Check the browser console for a giscus “discussion not found” error — that means a pathname mismatch (see step 4).
- Run
make build, thengrep -r disqus public/to confirm it’s gone.
One comment came through mapped to the wrong thread. I fixed the script, wiped the imported comments (the reset option earned its keep here), and re-ran the import with the corrected Disqus export XML. That sorted it out.
Done
After about an hour of work, I cut ties with Disqus. giscus works just fine, and I’m happy to run something less invasive.
Thanks to the maintainers for a great piece of open source software.
Mahalo 🌸
Comments