Self Hosted Hugo Comments: embedded in page

Having chosen to self-host my Hugo comments as part of the static page content, there are a couple of ways to do it. In this article, I explore embedding them in the page using shortcodes.

The method

Comments in my blog are represented by two shortcodes.


The first shortcode collects comment data in a semi-structured way and emits it as HTML. Here’s the whole thing.


{{- $postid := default (.Get 0) (.Get "id") -}}
{{- $username := default (.Get 1) (.Get "username") -}}
{{- $usericon := default (.Get 2) (.Get "usericon") -}}
{{- $postdate := default (.Get 3) (.Get "date") -}}

<div class="comment">
  <div class="user-info">
    {{ if $usericon }}
      <img src="{{$usericon}}">
    {{ end }}
    {{ $username }} {{ $postdate }}
  <div class="comments-menu"><a href=" Name:%0d%0aIcon:%0d%0aComment:&subject=Comment on {{ $.Page.Permalink }}?comment-id={{ $postid }}">Reply</a></div>
  <div class="comment-body">
    {{ .Inner }}

This code is triggered as, for example,

\{\{< comment id="100" username="Phil P" date="2021-10-21T16:54:40.122Z" >}}
You have working audio on your GNU/Linux laptop?  Must be nice.

  \{\{< comment id="101" username="Mark T. Tomczak" date="2021-10-21T17:56:00.084Z" >}}
  I used to, but I changed my window manager and now I'm not so sure. :-p
  \{\{< / comment >}}
\{\{< / comment >}}

Note that the approach supports nesting; a comment emitted into the Inner material of another comment is just copied along with the other content, so we just roll the comment tree up as we go.

Also worth noting in the definition of the shortcode is the mailto link. The link is constructed such that it auto-populates the body and the subject for easy adherence to the commenting policy; uesrs get started with a template email that will get me the right information to add their comment. Right now, this process is manual, but I’ve attempted to wire it up so it can be easily automated in the future.


A wrapper shortcode serves as an envelope for all the comments on the page and provides some placeholder text if there are no comments.


<div class="comments">
  <h1 class="comments">Comments</h1>
  <div class="comments-menu">
        <a href=" Name:%0d%0aIcon:%0d%0aComment:&subject=Comment on \{\{ $.Page.Permalink }}">
          Add comment
        <a href="/how-to-comment">How to comment</a>
  \{\{ if (not .Inner) }}
  <i>This article has no comments</i>
  \{\{ else }}
  \{\{ .Inner }}
  \{\{ end }}

This code injects the HTML to show a Comments section in the page; it constructs a mailto link like the Reply button does (exercise for the reader: both of those links can stand to be further consolidated into their own shortcode, since they’re so similar). It also provides a simple placeholder text if there are not yet any comments.

Overall, I’m not at all unhappy with the result! (Editor’s note: I haven’t done a CSS pass on this yet, this just shows structure).

Two comments inside the comments section

Pros and cons

Overall, I’m not unhappy with this approach, but it has some tradeoffs.


Thread flow is very clear
The fact replies are nested means it’s easy to see the flow of the conversation by reading the markdown itself.
Comment text is just markup
The comment text is just inline HTML, so relatively easy-to-read. It also supports everything I could possibly want to support (abuse of this—after all, it’s user-supplied content directly injected into the page—is moderated by the fact that the comments are hand-stitched into the page by me).


Mixes content and presentation
To support this approach, we need the \{\{< comments >}} shortcode on every page. Even though Hugo supports template pages (archetypes), that’s a maintenance burden. Ideally, stitching comments into the page should be the job of the layout itself, while the comments would be metadata for the page.
Markup, not markdown
Because shortcodes output HTML, I can’t use Markdown for the body of the comments; trying to pass a comment with replies through the Markdown parser mangles the HTML emitted by the nested \{\{< comment >}} shortcodes. Markdown is easier to work with and further constrains the styling in a nice way, which I enjoy.
Lack of consolidation
There’s benefit to having comments consolidated in one place for management. For example, adding a new comment with this approach requires mapping from the Subject line of an email to the relevant URL (and comment ID). If comments were consolidated in one place, adding new comments would be a simple append operation.

What’s next?

This approach is working for now, but I’m going to pursue moving the comments into page front-matter (or even a central data file that is read once and built into a scannable structure).