Staticman: An Alternative to Disqus for Comments on Static Sites

Staticman: An Alternative to Disqus for Comments on Static Sites

Comments are an important aspect of many websites, particularly blogs, whose success depends on their ability to create communities. However, including comments is inherently more difficult for static websites than for dynamic websites (e.g. managed through Wordpress). With Hugo, comments can be easily integrated via Disqus. The disadvantage, however, is that foreign JavaScript code needs to be executed and that the comments are not part of the page itself. Here, I will explain how comments can be integrated into a web page using Staticman.

What is Staticman?

Staticman is an API, which allows for the integration of comments into static web pages. What is the idea of this approach?

  • To create a new comment, a request is sent to the Staticman API.
  • If the moderation option is set to true, a Staticman GitHub account will automatically create a pull request for the submitted comment. The user can then decide whether to merge the pull request or close it. If the moderation option is set to false, comments are automatically merged into the page.
  • The static web page is rebuilt and the comments are displayed.

Problems with the Staticman API

Unfortunately, it seems that the public Staticman API is overstrained. When I wanted to use the API, the Staticman GitHub account would not accept my collaboration invitation quoting “Invitation not found” when trying to connect to the API as described in the official documentation. It turned out that many other people also had the same problem. Luckily, the problem can be overcome by hosting your own version of the Staticman API. So this is what I did.

Hosting your own Staticman API

To host my own version of the Staticman API, I’ve followed this great tutorial. Here, I’ll briefly summarize the required steps for getting Staticman running for a GitHub repository.

  1. Create a new GitHub account for the Staticman API
  2. Create a GitHub token for this account, allowing API/write access
  3. Clone an instance of Staticman
  4. In the staticman folder, create a file called Procfile and store a single line in there: npm start
  5. Create a private RSA key for use with the API: openssl genrsa -out key.pem
  6. Create an account at Heroku, a free web app hoster and download the Heroku CLI
  7. After logging into the CLI using heroku login, create a new Staticman app and configure it: cd ~/staticman heroku create {nameOfYourStaticmanApp} $heroku config:set NODE_ENV="production" $heroku config:set RSA_PRIVATE_KEY="$(cat key.pem)" $heroku config:set GITHUB_TOKEN="Your_Token"
  8. Create a production branch: git checkout -b production origin/dev
  9. Add config.production.json to .gitignore
  10. Commit the changes: git add config.production.json Procfile .gitignore git commit -m "Set up Staticman v3 for deployment to Heroku"
  11. Deploy the API: git push heroku production:master
  12. After the API has been built successfully, you should be greeted with Hello from Staticman version 3.0.0! when visiting your API instance at https://{nameOfYourStaticmanApp}

Limitations of Heroku apps

Free Heroku apps come with some limitations. The most important limitation is that the number of free dyno hours is limited. A dyno is the isolated container in which your application is running. A free dyno begins to sleep after 30 mins of inactivity. Otherwise, it is always on as long as you still have remaining dyno hours (currently 550 free dyno hours per month).

When your free dyno hours are exhausted, all of your free dynos start sleeping. You will receive a notification from Heroku before this happens though. If the only app that you are running is Staticman, however, it is unlikely that you will use up you free dyno hours because this would mean that you are receiving comments for more than 18 hours per day, which would be quite a lot.

So, the main disadvantage of free dynos when is that posting comments will be slow (delay of a few seconds) when no one has posted anything within a while (30 minutes). In my opinion, this limitation is sufferable for a free service. If you want to get rid of this limitation or if you need more dyno hours, you can always upgrade to a paid dyno.

Reconfiguring your theme

Once the API is running, you need to adjust your Hugo theme to let users submit comments through the API and have the available comments in your data folder displayed. There are already several tutorials available that deal with this problem, for example, here and here.

Since I had some problems with these tutorial, I’d recommend starting with the commenting system from the official demo site. I’ll shortly go through the most important aspects:

  • The configuration of the API is done via staticman.yml. Adjust these values according to your needs.
  • Adjust the CSS for the comments to your needs.
  • Adjust the template responsible for comments in your theme folder using the templates from the demo.

When I tried to get the comments template working, I couldn’t get any comments displayed because the unique IDs never matched. Thus, I’ve ended up with a modified partial template for the Staticman commenting system. The idea of my approach is to store the path of the page in the comment file and then to check whether the comment’s path is a subset of the page’s path via {{ if in .path $.File.Path }}.

Adding a comment counter

A nice addition to the system would be to show the number of comments associated with every post in the list view. This can be easily achieved by using the hasComments variable defined in the commenting template. We simply create the following partial template for the comment counter, which displays a representative SVG as well as the count using hasComments.

<div class="meta__item-comments meta__item">
    <svg class="meta__icon icon icon-comments" width="20" height="20" viewBox="0 0 20 20">
    <path d="M14.999,8.543c0,0.229-0.188,0.417-0.416,0.417H5.417C5.187,8.959,5,8.772,5,8.543s0.188-0.417,0.417-0.417h9.167C14.812,8.126,14.999,8.314,14.999,8.543 M12.037,10.213H5.417C5.187,10.213,5,10.4,5,10.63c0,0.229,0.188,0.416,0.417,0.416h6.621c0.229,0,0.416-0.188,0.416-0.416C12.453,10.4,12.266,10.213,12.037,10.213 M14.583,6.046H5.417C5.187,6.046,5,6.233,5,6.463c0,0.229,0.188,0.417,0.417,0.417h9.167c0.229,0,0.416-0.188,0.416-0.417C14.999,6.233,14.812,6.046,14.583,6.046 M17.916,3.542v10c0,0.229-0.188,0.417-0.417,0.417H9.373l-2.829,2.796c-0.117,0.116-0.71,0.297-0.71-0.296v-2.5H2.5c-0.229,0-0.417-0.188-0.417-0.417v-10c0-0.229,0.188-0.417,0.417-0.417h15C17.729,3.126,17.916,3.313,17.916,3.542 M17.083,3.959H2.917v9.167H6.25c0.229,0,0.417,0.187,0.417,0.416v1.919l2.242-2.215c0.079-0.077,0.184-0.12,0.294-0.12h7.881V3.959z"></path></svg>
    {{ $nbrOfComments := 0 }}
    {{- if ( $.Scratch.Get "hasComments") }}
        {{ $nbrOfComments = $.Scratch.Get "hasComments" }}
    {{- end }}
    <span class="meta__text">{{ $nbrOfComments }}</span>

Then, we simply include the created file in the template that displays the post meta information with the following command:

{{- partial "post_meta/comments.html" $root -}}

Remaining issues

The only remaining issue I have is that it doesn’t seem to be possible to load comments from subfolders of data/comments. It seems that Hugo doesn’t allow access to files when looping over diffferent folders because file access seems to require specific paths rather than dynamic paths. For example, you can access the comment files using .Site.Data.comments but you wouldn’t be able to access files by constructing a path via print .Site.Data.comments <pathToDirectory>.

Thus, all comments of a site have to be stored in a single folder, which could be a problem if there are thousands of comments. For small sites, however, this should be fine.

I hope this overview helped you getting started with Staticman for Hugo. Many thanks to Eduardo Bouças for this great tool! If you have any questions, just write a comment below ;-)

Author: Matthias Döring


Vincent Tam
02 Nov 18 22:43 UTC

Your tutorial deserves a static comment.

As the author of the linked guide, I would like to point out that (self-hosted) GitLab is supported as well in the new development version of Staticman. In fact, that’s the goal for writing my series of tutorials a month ago: to summarize my efforts for deploying eduardoboucas/staticman#219. To allow common netizens to understand the work of Nicolas Tsim, who actually developped Staticman’s GitLab support, I’ve built a minimal Hugo GitLab Pages. Staticman’s public development server can’t be used for that purpose because Eduardo Bouças has never disclosed its associated GitLab account. Idealistically, this PR would be merged into the master branch after others have successfully ran his code.

Coincidently, Staticman’s public production API server is reaching its limit, and I’ve (re-)proposed hosting a custom API instance as an ad-hoc solution. The true motive is to test Staticman v3’s functioning with GitHub. As your test results suggest, your instance is running v3, so I suggest you to change the form action URL from v2 to v3.

In terms of the technical setup, here’s some points that I would like to add:

  1. Creation of a root-level Procfilewith web: npm start is the key to get the whole thing works.
  2. I would rather say exclude config.production.json from .gitignore for clarity.

Regarding the last section, it is possible to store Staticman’s generated YML files in different (sub)folders. The path and property name can be set in staticman.yml, whereas the subpath is controlled by the slug name. You may view my aforementioned sample Hugo site for details.

Matthias Döring
04 Nov 18 08:27 UTC

Dear Vincent, thanks for the comprehensive, static response. The direct support of GitLab pages with the new development version of Staticman sounds neat. I didn’t cover this possibility in this post though because I’m currently using Netlify rather than GitLab for deployment.

Regarding the API version: I also thought that the Staticman API was at version 3.0. But I guess that the main branch, which I cloned, is still at version 2.0. because when I try to do a POST request with v3 I get invalidVersion via the errorCode field.

Regarding Staticman and folders: It’s true that it is possible to store the files in different folders, I also did that. The problem I had, however, was accessing these comments using Hugo because file access there seems to require specific paths but not dynamic paths. For example, you can access the comment files using .Site.Data.comments but you wouldn’t be able to access files if you construct a filename using something like print .Site.Data.comments <pathToDirectory>.

Thanks for the additions as well. I’ll update the post and mention the Procfile!

Vincent Tam
08 Nov 18 21:52 UTC

#8 suggests that production comes from origin/dev. I checked your API instance. Since the message “version 3.0.0” comes out, it should be v3 unless you touched the NodeJS code—I did play with that to understand how Nicolas Tsim’s PR worked. In fact, without his help, I wouldn’t had managed to get Staticman working on localhost.

Regarding the invalidVersion error, please ensure that you’ve set up the form action according to Staticman v3’s URL scheme described, again, in eduardoboucas/staticman#219—an additional Git service provider is needed in the request URL: either github or gitlab for the moment. Without this, a 400/500 error will be thrown. You may view my sample GitHub/GitLab sites for a minimal working examples. Each of them contains a working URL in their form action.

Last but not least, free Heroku dynos should never be used for production services. I regret not having stated this clearly in my guide. I’m using a free Heroku dyno to test Nicolas Tsim’s PR219, hoping that this would get merged against origin/master from origin/dev. However, it seems that you’re using Staticman for your professional blog, so you may expect your free dyno to idle from time to time.

Anyways, that’s already a great leap since now we own our comments. This allows better $\rm \LaTeX$ support, which is not available on Facebook, WordPress, Disqus, etc. Changing this to KaTeX and GitLab is even better.

In terms of CMS integration, Netlify has a great support for that. A simple search should return relevant official news from both corporations. Nonetheless, it’s better to avoid proprietary technologies so as to avoid being tied with them. Staticman is a tool for gaining control over your comments, while GitLab CE is a tool for gaining control over version control.

Good luck for your Staticman !

Matthias Döring
10 Nov 18 11:17 UTC

Thanks for checking this. I just read the documentation about Staticman V3 on GitHub, so it should not be a problem to use the V3 instance. I just didn’t know that the config/format for API requests had changed.

I also didn’t know the limitations of the free Heroku dynos before. Actually, I was noticing the idling and wondering why this happens. Now it makes sense to me since they are going to sleep if they are inactive. I don’t really think this is a problem though because the dyno usually wakes up within a couple of seconds, which is not limiting the usability of Staticman for commenting too much.

Keep up the good work :-)

10 Nov 18 12:34 UTC

Nice write up thank you.

10 Nov 18 12:35 UTC

Testing the speed now that the dyno instance has been triggered!

Vincent Tam
13 Nov 18 15:04 UTC

To be more precise, you may expect a delay of about 15–20 seconds when the “submit” button when a free dyno is waking up. I don’t think that’s a problem for personal uses.

10 Jan 19 10:46 UTC

You can use subfolders. Just access them in the template with “index .Site.Data.comments $subfolder” (.Site.Data.comments is just a map, and the “index” function allows you to access arbitrary map elements by key)

I haven’t tried this yet but to fix the sleeping dyno problem, I’m thinking of adding a bit of javascript to ping my server when the user starts typing a comment. Hopefully by the time they’re done the server will be awake.

24 May 20 06:34 UTC

Good post

29 May 20 14:42 UTC


Leave a comment

Please enter your information. Your email address will not be published.

Thank you

Your comment has been submitted and will be published once it has been approved.


Your post has not been submitted. Please return to the form and make sure that all fields are entered. Thank You!