While looking up for a 3rd party commenting service for my Hugo theme I stumbled upon Darek Kay’s recommendations and after excluding all the other services because I knew they wouldn’t fit my requirements, I gave Chirpy.dev a try. I wasn’t dissapointed.
At the moment of writing Chirpy is free to use but things may change in the future, maybe some features will break in that case. Contact me if you have any problems.
Basic Integration
Just follow what the site says:
In your theme then include the <script></script>
in your <head>
section, you can also optimize it in a way to only load it if you are in a page, for example:
{{ if .IsPage }}
<script defer src="https://chirpy.dev/bootstrap/comment.js" data-chirpy-domain="your.domain.here"></script>
{{ end }}
Then in your page template place the div
wherever you like:
<div data-chirpy-comment="true"></div>
Dynamic loading
To improve privacy and loading times of your pages we can improve our implementation a little bit.
We can put a button that loads the script when a visitor clicks on it. This way we can save some network requests while improving privacy at the same time.
Wherever you want to place your comment widget place this:
<input type="button"
onclick="chirpyloader()"
value="Load comments"
id="load-comments">
<script language="JavaScript">
function chirpyloader()
{
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'https://chirpy.dev/bootstrap/comment.js';
script.dataset.chirpyDomain="your.domain.here"
head.appendChild(script);
document.getElementById("load-comments").style.display = 'none';
}
</script>
<div data-chirpy-comment="true"></div>
This version doesn’t need to place elements in your <head>
section.
We improve our user experience on one side but at the same time our visitors need to click a button and to wait for the widget to load before checking eventual comments. We are sort-of fixing this in the next iteration.
Dynamic loading with comment counter
This part is specific for Hugo, it uses the getJSON
function to query the Chirpy server to check for comments. This is sort-of a hack because this API is not documented and it can break at any time (e.g. different endpoints or rate limiting for unpaid users).
Our endpoint at the moment is https://chirpy.dev/api/trpc/comment.forest
. It simply responds to percent encoded JSONs in the query’s parameters.
{{ $commentCounter := 0 }}
{{ $jsonreq := ( printf "%s" (dict "json" (dict "url" .Permalink ) | jsonify) ) }}
{{ $url := printf "https://chirpy.dev/api/trpc/comment.forest?input=%s" ( urlquery $jsonreq) }}
{{ with $dataJ := getJSON $url }}
{{ with .Err }}
{{ warnf "%s" .}}
{{ $commentCounter = "N/A" }}
{{else }}
{{ $commentCounter = len $dataJ.result.data.json }}
{{ end }}
{{ end }}
<input type="button"
onclick="chirpyLoader()"
value="Load {{ $commentCounter }} comment(s)"
id="load-comments"
>
<script language="JavaScript">
function chirpyLoader()
{
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'https://chirpy.dev/bootstrap/comment.js';
script.dataset.chirpyDomain="your.domain.here"
head.appendChild(script);
document.getElementById("load-comments").style.display = 'none';
}
</script>
<div data-chirpy-comment="true"></div>
Keep in mind that Hugo is going to make a lot of requests to Chirpy’s server for every render. The “bursty” nature of these requests makes them a pain for free services to handle at scale. Unfortunately we need to continuosly render our site at every update or interval to feed the comment counter with fresh data. I opted for a 24 hour interval because I don’t have that many interactions. Just use common sense when dealing with this sort of things.
As you can see (I hope it didn’t break in the meantime), I placed a little timer that informs the visitor of the last counter refresh.
<small>Last update:
<relative-time
datetime={{ now.Format "2006-01-02T15:04:05-0700" }}
tense="past">
{{ now.Format (.Site.Params.dateFormat | default "Jan 02, 2006") }}
</relative-time>
</small>
The relative-time
is a custom element created made with github/relative-time-element, if you don’t want to include it your implementation can be something like:
<small>
Last update: {{ now.Format (.Site.Params.dateFormat | default "Jan 02, 2006") }}
</small>
Like for the requests to Chirpy’s server keep in mind that continuosly rendering the “last refresh” section can be detrimental to your hosting service because you are updating every page and hammering their servers with basically meaningless updates. If you self-host your static website then put whatever refresh interval you like, at this point you could even self-host Chirpy or other solutions
Debugging issues
Hugo has a cache mechanism for the getJSON function, this can create weird issues while developing. To fix this you can override the cache timeout:
[caches]
[caches.getjson]
maxAge = "1m"