Staticman: An Alternative to Disqus for Comments on Static Sites
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.
- Create a new GitHub account for the Staticman API
- Create a GitHub token for this account, allowing API/write access
- Clone an instance of Staticman
- In the staticman folder, create a file called
Procfile
and store a single line in there:npm start
- Create a private RSA key for use with the API:
openssl genrsa -out key.pem
- Create an account at Heroku, a free web app hoster and download the Heroku CLI
- 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"
- Create a production branch:
git checkout -b production origin/dev
- Add
config.production.json
to.gitignore
- Commit the changes:
git add config.production.json Procfile .gitignore
git commit -m "Set up Staticman v3 for deployment to Heroku"
- Deploy the API:
git push heroku production:master
- 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}.herokuapp.com
.
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>
</div>
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 ;-)
Comments
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 fromv2
tov3
.In terms of the technical setup, here’s some points that I would like to add:
Procfile
withweb: npm start
is the key to get the whole thing works.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 instaticman.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 theerrorCode
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 likeprint .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 fromorigin/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: eithergithub
orgitlab
for the moment. Without this, a400/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 formaction
.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
fromorigin/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 :-)
Justin
10 Nov 18 12:34 UTC
Justin
10 Nov 18 12:35 UTC
Vincent Tam
13 Nov 18 15:04 UTC
peko
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.
Layomop
24 May 20 06:34 UTC
rubybox
29 May 20 14:42 UTC