Add self hosted comments to your Gatsby blog using Schnack

If you want to host your own comments on your Gatsby blog, there are a few options out there. One of them that has not been documented well yet is using the open source project Schnack.

Overview

Schnack is open source, and runs on Node and Express. It stores all data in a tiny local SQLite database, and it supports importing existing comments from Disqus or WordPress via a generated XML file.

Authentication is already built in and you can easily require users to log in via Github, Twitter, Google, and Facebook before posting comments.

You can moderate comments very easily by putting them in a wait queue, and you can also trust friends to allow them to leave comments without moderation.

Finally, there are some plugins to allow for extending the functionality of the app, such as notifications for new comments via Slack and email.

Requirements

You need to be able to run a Node app on your server. In my case I’m using Digital Ocean, but any server that can run a Node app would work.

Caveats

While Schnack is written in JavaScript, the render logic is outside of React. It uses JS templates, and querySelector logic to write to the DOM. It actually works quite well, though it isn’t pure React. The project is also not actively maintained.

Ok, so with that out of the way, here we go. We will 1st, configure and run a Schnack server, and 2nd, configuring Gatsby to work with Schnack.

1. Configure and run a Schnack server

1.1 Clone and configure Schnack

On your sever, clone the repo git clone https://github.com/schn4ck/schnack and follow the configuration guide on the Schnack website to npm install requirements and get it working. It’s well documented already. You basically copy the default config.tpl.json to config.json and modify the settings to match your system.

In my case I have both Github and Twitter OAuth configured. Make sure to add your own user ids to the admins array. This will allow you to approve/reject comments sitting in the moderation queue.

Keep track of the port number you assign to your server in the config file. We’ll be using port 9999 in this example.

Here is a copy of the config file we’ll use for this tutorial for your reference.

// config.json
{
  "schnack_host": "https://comments.YOUR_DOMAIN.TLD",
  "page_url": "https://YOUR_DOMAIN.TLD/%SLUG%",
  "database": {
    "comments": "comments.db",
    "sessions": "sessions.db"
  },
  "port": 9999,
  "allow_origin": ["http://localhost:9999"],
  "admins": [1],
  "oauth": {
    "secret": "MAKE_UP_A_KEY",
    "twitter": {
      "consumer_key": "YOUR_TWITTER_APP_KEY",
      "consumer_secret": "YOUR_TWITTER_APP_SECRET"
    },
    "github": {
      "client_id": "YOUR_GITHUB_APP_ID",
      "client_secret": "YOUR_GITHUB_APP_SECRET"
    }
  },
  "notify": {
    "slack": {
      "webhook_url": "https://hooks.slack.com/services/YOUR_SLACK_WEBHOOK_PATH"
    }
  },
  "date_format": "MMMM DD, YYYY - h:mm a"
}

1.2 Start the node server

To test things out, first just run npm i && npm start on the server. If all works well, and you don’t get any errors, you’re ready to go. Otherwise fix any errors first then continue.

You should now be able to hit http://YOUR_SERVER_IP:9999 and see a simple {"test":"ok"} message in the browser. We’ll change this IP to your domain and a default SSL port in step 4.

1.3 Set up a server monitor

Next we’ll set up a service to monitor and keep the Node server running at all times. Schnack comes with nodemon, although I recommend using pm2 for this. Just install it using npm install pm2 -g and you’re ready to go.

To get the daemon running, cd into your Schnack root folder and run pm2 start index.js --name GIVE_YOUR_SERVICE_A_NAME

Verify the service is running using pm2 list which will show you the service you just created with a status of online and other details like memory and CPU usage.

If you run into errors, use pm2 logs to inspect and fix any configuration issues.

1.4 Configure Nginx

In this step we’ll configure nginx to proxy requests to the Node server so you can access the comments server from https://comments.YOUR_DOMAIN.TLD

You will need an SSL certificate for comments.YOUR_DOMAIN.TLD and you can use Let’s Encrypt to generate free SSL certificates yourself.

Let’s assume you are running your Schnack node server on port 9999. In this setup the nginx configuration file will forward that port to comments.YOUR_DOMAIN.TLD where the comments service will be publicly accessible.

# /etc/nginx/sites-available/YOUR_DOMAIN.TLD.conf
 
# Node server port, will proxy to your server below
upstream GIVE_THIS_A_NAME {
    server 127.0.0.1:9999;
    keepalive 64;
}
 
# Public server address
server {
  listen 443 ssl;
  server_name comments.YOUR_DOMAIN.TLD;
 
  ssl_certificate /PATH/TO/YOUR/CERTIFICATE/FILE.pem;
  ssl_certificate_key /PATH/TO/YOUR/KEY/FILE.pem;
  include /PATH/TO/YOUR/LETS/ENCRYPT.conf;
  ssl_dhparam /PATH/TO/YOUR/DH/PARAMS.pem;
 
  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_pass http://GIVE_THIS_A_NAME/;
    proxy_redirect off;
    proxy_read_timeout 240s;
  }
}

At this point you should be able to restart nginx and see your comments server running at https://comments.YOUR_DOMAIN.TLD

2. Configuring Gatsby to work with Schnack

Now you will include the embed.js js file from the Schnack server into your Gatsby blog posts, and write a React component to render the comments.

2.1 Install helmet dependencies

We want to add the the script file from the Schnack server to each blog post and pass it a unique slug for each post.

The simplest way to import an external script file in Gatsby is to use Helmet which requires the gatsby-plugin-react-helmet plugin as well. Just add these node modules to your gatsby project, if you don’t have them already.

npm install --save gatsby-plugin-react-helmet react-helmet

2.2 Write a Comments component

Now we’ll create a component that will be rendering the comments. Note I’m leaving out all styling out of this post to keep things short. Feel free to style things as you wish.

// src/components/Comments.js
import React from "react";
import PropTypes from "prop-types";
import { Helmet } from "react-helmet";
const Comments = ({ postSlug }) => (
  <>
    <Helmet>
      <script
        async
        src={`${process.env.COMMENTS_SERVER_URL}/embed.js`}
        data-schnack-slug={postSlug}
        data-schnack-target=".blog-post-comments"
      />
    </Helmet>
    <section className="blog-comments">
      <h3 className="blog-comments-headline">Comments</h3>
      <div id="blog-post-comments" className="blog-post-comments" />
    </section>
  </>
);
Comments.propTypes = {
  postSlug: PropTypes.string.isRequired,
};
export default Comments;

2.3 Add the Comments component to the Post template

Now that we have a the component, all that’s left to do is add it to the post template. The only prop we need to pass is the slug for the post.

// src/templates/post.js
import Comments from "./Comments";
import Layout from "./Layout";
const Post = ({ post }) => (
  <Layout>
    {post.body}
    <Comments postSlug={post.frontmatter.postSlug} />
  </Layout>
);
export default Post;

Done!

You now should be able to see some buttons on your post asking you to login. Depending on which oauth configurations you set up in your Schnack config.json file. Once you log in, you will see a form to submit a new comment. You can even preview the comment and use markdown to format the comment body.

You can see an example of this just below here. If you have any questions leave me a comment below and I will try to help.