Adding Comments to a Static Astro Website with Remark 42

Adding Comments to a Static Astro Website with Remark 42

Static Site Generators like Astro are a simple and quick way to spin up a static website and get it live. However, adding more advanced features might become challenging with all the options available. Today, we will explore one way to add the comments functionality to a static website, based on Astro, with a self-hosted Remark42 service.

Adding Comments to a Static Astro Website with Remark 42

First, what is Astro?

Undoubtedly, Astro doesn’t need an introduction, but if you hear it for the first time, it will be sufficient to say that Astro is a modern framework for building static websites. It is server-side rendered and doesn’t ship any JavaScript framework by default. The pages are static, easy to load, and remind of the old days, when websites were made with PHP and Drupal.

With this philosophy in mind, it is quite simple to turn the generated static pages into a fully-fledged client-side application using any framework of your choice. Most of the popular frontend framework integrations, such as React, are available out of the box with the extensions provided by the vast community. Talking about React, if you are into it, you should definitely check out these delightsome React Tutorials, which are updated daily and available absolutely for free.

Astro allows you to enrich HTML with interactivity only where and when necessary, putting you fully in control of your website’s performance and Lighthouse score.

Choosing a Comment Solution

If you Google available commenting solutions for an Astro website, you might get the impression that there are plenty of solutions available. Generally, you can split them into 3 categories:

  • Free hosted solutions. Disqus is the most notorious example. I’m not going to lie – I used to have the Disqus commenting block right in this blog. While providing amazing commenting value, performance and privacy implications turned out to be a common issue. Believe it or not, there was one guy who compared the Network panel of Chrome Developer Tools with and without Disqus, and the difference was measured literally in hundreds of unnecessary HTTP requests. With this knowledge in mind, I deliberately left out the comments section while migrating this blog to Astro.
  • Paid hosted solutions. Good examples are Commento or Commentbox. Having initially invested most of my available time in Commentbox, I was disappointed to find that there is absolutely no way to change the styles. Even the most prominent green buttons cannot be re-painted. Commento turned me off because of the news about its development getting to a stop. Looking at the docs now, I see that Commento has the data-css-override configuration property, which allows the specification of a URL to the custom CSS file to override the default styles. If that actually works, Commento might also be worth giving a shot.
  • Free self-hosted solutions. Commento also qualifies for this section. However, it requires an instance of PostgreSQL, and I didn’t want to go this far at this point. There were also a few libraries that used GitHub issues for the comments. They are free and easy to set up but require all commentators to have a GitHub account. Eventually, I went with Remark42, and here is why I did that.

Why Remark 42

After checking their GitHub repository, I found that it seems to be in shape and well-maintained. Remark42 has a wide range of functionality, including a great notification system (email or Telegram), and supports many 3-rd party authentication providers. As well as anonymous commenting. It even supports Apple, which I didn’t implement due to the requirement of having a paid Apple development membership.

The backend part is written in Go, and the frontend part is written in React. While being proficient in the latter, I’m currently heavily investing in learning the Go ecosystem, and it seemed like a perfect stack from my point of view.

For a database, a tool called bbolt (previously, Bolt DB) is used, which is a key/value storage for Go applications. It stores the data on the filesystem and is easy to back up and migrate. That sounded good enough for me, because I did not expect a rush of comments any time soon.

Lastly, the documentation of Remark42 is great. It includes step-by-step guides on configuring the authentication and setting up the service in Docker.

Of course, cons are also present, and some of them are:

  • Not customizable styles. When asked, ChatGPT hallucinated about a magical configuration parameter to specify the link with custom styles, but in reality, it is not yet supported. I managed to customize the styles using a workaround, more on which will be provided below
  • Since it still requires persistence, it’s not possible to host it cheaply in the cloud, where you would spend only based on the usage. As I mentioned, I wasn’t expecting huge traffic any time soon, so hosting it in Google Cloud Run or AWS Fargate would be perfect. But no
  • The comments are rendered on the client. The server renders only a “div” with a specific ID. This may not carry the SEO weight that user comments usually bring.

Picking a Hosting

I have run a Digital Ocean droplet to host this blog for almost a decade. Not so long ago, I completely migrated it to Astro, moved on to Netlify, and finally was able to kill it and stop paying $14.4 every month.

After checking out the pricing for virtual machines in AWS and GCP, I became a loyal DO customer once again, as they still seem to have the cheapest virtual machines out there. - Eventually, I went with a $6 regular Droplet with 1 CPU core and 1 GB of RAM. Given that the website I was adding comments to has few visitors, the machine is mostly idle.

Technical Implementation

The official documentation provided examples of running Remark42 as a service in Docker Compose, and that’s where I started. However, I needed to update the styles of the web interface before doing anything further. With a little trial and error, I concocted a simple docker-compose.yml, based on the original, to run it locally:

version: "2"

services:
  remark:
    build: ./remark42
    container_name: "remark42"
    hostname: "remark42"
    restart: always

    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"

    ports:
      - "8080:8080"

    environment:
      - REMARK_URL=http://localhost:8080
      - SITE=reactlibs
      - SECRET=secret
      - DEBUG=true
      - AUTH_ANON=true
      - AUTH_GOOGLE_CID
      - AUTH_GOOGLE_CSEC
      - AUTH_GITHUB_CID
      - AUTH_GITHUB_CSEC
      - AUTH_FACEBOOK_CID
      - AUTH_FACEBOOK_CSEC
      - AUTH_DISQUS_CID
      - AUTH_DISQUS_CSEC
      - STORE_BOLT_PATH=/srv/var/db
      - BACKUP_PATH=/srv/var/backup
      - AUTH_TELEGRAM=true
      - NOTIFY_ADMINS=telegram
      - NOTIFY_TELEGRAM_CHAN=-111
      - TELEGRAM_TOKEN=111:AAF
    volumes:
      - ./var:/srv/var
#      - ./remark42/web:/srv/web

After running docker compose up, we can access the Remark42 demo page at http://localhost:8080/web/.

Overriding Styles

Inside the directory with the docker-compose.yml I also have the remark42 directory which contains the following Dockerfile:

FROM umputun/remark42:v1.13.1

COPY ./web /srv/web/

It uses the official remark42 Docker image as the base image but copies the style overrides, stored in ./remark42/web directory, on top of it.

After spinning up a vanilla version of Remark42 and looking around, it figured that the web-facing assets are stored in /srv/web. Among them, there are some CSS files (global.css, remark.css, last-comments.css) that can be updated to improve the look and feel. I also bothered to include my custom fonts in the comment’s iframe. It is easily achievable by adding a few @font-face blocks to global.css and providing the fonts folder with the necessary fonts.

CSS variables can and should also be changed. Just keep in mind that their CSS is not well-optimized, and you need to search carefully and make sure that you made your changes in all possible places.

My development workflow was the following:

  • Remove the contents of the ./remark42/web directory on the host and spin up the project.
  • Copy the contents of the /srv/web directory from the container to the host by running docker cp remark42:/srv/web ./remark42/web
  • Uncomment the last line in docker-compose.yml (./remark42/web:/srv/web) to mount the volume
  • Edit the files in ./remark42/web from your IDE and observe the changes by refreshing http://localhost:8080/web/

Once happy with the styles, you can keep the overrides in your Git, and they will be automatically picked up during the next container rebuild. Make sure that your base image version is statically set (v1.13.1 in my case) because updates may change the asset’s structure, and the comments will lose their beauty.

Note that if you mess something up, you always can rebuild the image from scratch and start overriding anew.

Running in Production

Previously, I used to have Traefik handling my incoming requests. Not only it was able to route different requests to different containers and the specified rate, but it also magically handled all the SSL-business for me, automatically creating and reviewing certificate with Let’s Encrypt.

It’s been a few years and two major Traefik versions since my last configuration attempts. The base principles have remained the same, though: you have the Traefik load balancer on the front, which accepts all incoming requests and decides where to route them based on the configuration labels set on another services. Here goes the excerpt from the production docker-compose.yml, which sets up Remark42 on comments.mydomain.com:

version: "3.8"

services:
  comments:
    container_name: "comments_remark42"
    restart: unless-stopped
    build:
      context: ./remark42
    logging:
      driver: json-file
      options:
        max-size: 50m
    volumes:
      - ./var:/srv/var
    environment:
      # Your Remark42 configuration goes here
    labels:
      - 'traefik.http.routers.comments-router.rule=Host(`comments.mydomain.com`)'
      - 'traefik.http.routers.comments-router.service=comments-service'
      - 'traefik.http.services.comments-service.loadbalancer.server.port=8080'
      - "traefik.http.routers.comments-router.entrypoints=websecure"
      - "traefik.http.routers.comments-router.tls.certresolver=myresolver"

  traefik:
    image: traefik:v3.1.4
    container_name: "comments_traefik"
    command:
      - "--log.level=ERROR"
      - "--api.insecure=false"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=true"
      - "--entryPoints.web.address=:80"
      - "--entryPoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.email=your.email@gmail.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
    ports:
      - "80:80"
      - "443:443"
    logging:
      driver: json-file
      options:
        max-size: 50m
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./letsencrypt:/letsencrypt

Here are a few things to be aware of here:

  • comments-router and comments-service are arbitrary names, but you need to keep them consistent in the configuration
  • It is required to mount a volume for the Lets Encrypt certificates to persist them between container restarts
  • Logs from containers accumulate and may eat up your droplet’s entire space after a few years. Therefore, it is necessary to limit the storage for logs to, say, 50 MB, as per the example

If you run docker compose up on this configuration, you will end up with Remark42 available at https://comments.mydomain.com, given that you have properly configured the A DNS record in your domain registrar’s panel.

Adding Comments to the Astro Website

Now that the comments service is running, the final and probably the easiest part is to add the comments widget to your Astro website.

To make things super-simple, I created a separate Astro component for Comments:

<script is:inline>
    var remark_config = {
        host: 'https://comments.mycomments.com', // Where your comments are runnign
        site_id: 'reactlibs', // Corresponds the SITE environment variable in docker-compose.yml config.
        show_rss_subscription: false, // Waste of space, but enable it if you need it.
        no_footer: true, // Of course, you don't want to share what's powering your comments.
    }
    !function (e, n) {
        for (var o = 0; o < e.length; o++) {
            var r = n.createElement("script"), c = ".js", d = n.head || n.body;
            "noModule" in r ? (r.type = "module", c = ".mjs") : r.async = !0, r.defer = !0, r.src = remark_config.host + "/web/" + e[o] + c, d.appendChild(r)
        }
    }(remark_config.components || ["embed"], document);
</script>

<div id="remark42" class="mt-6 -mx-3"></div>

Then, you simply render <Comments /> in the place where you need them – typically after the content block – and that’s basically it. The comments should be rendered where you need them.

Many more things could be described here – such as setting up the authentication methods and assigning the admin user – but I believe the official documentation handled that well. Feel free to refer to it for any missing information.

Challenges

I can’t really say that I met some real blockers, but not everything went smoothly, and here are a few things that are worth mentioning.

Configuring the DNS of a subdomain

The issue is very subjective and totally depends on the registrar’s service. I’m using Moniker, with which creating a subdomain and pointing it to a DO droplet required some continuous Googling. Long story short, there are the steps needed to be done:

  • Find the IP address of your droplet in the DO administrator panel. It should be pretty easy to find
  • Sign-in to your registrar and find the DNS management panel for the root domain. For me it was: My account → Domain → Modify domain setup.
  • Add an A record with the subdomain’s name. For comments.mydomain.com, it should be comments. Paste the IP address of your droplet as the value.
  • Click Add/Save/Submit and wait until it takes effect.

Configuring the Microsoft Authentication

If you haven’t used Azure before, you’d need to take some time to set up an account. It requires filling out a certain amount of formal forms, providing a credit card, probably a phone number, and whatnot. Overall, the instructions in the documentation are correct, but after following them in full, I was facing the “request not valid” error. After some time spent Googling, here is the SO answer that actually helped:

You can simply go to the portal Azure panel, open the Manifest section, and change the signInAudience’s value from PersonalMicrosoftAccount to AzureADandPersonalMicrosoftAccount. If it doesn’t exist, create both key and value.

In the Microsoft Entra admin center, within your application, locate the manifest section and set the signInAudience to AzureADandPersonalMicrosoftAccount. Save/Upload it – and Microsoft Authentication should start working.

Twitter Authentication

TLDR; wasn’t able to set up a twitter/X integration at all.

The error said failed to get request token and after short local debugging, it appeared that Twitter was rejecting authorization requests. I triple-checked the client ID and client secret, but everything seemed to be in order. Eventually, I gave up and created an issue on Remark42’s Github, mentioning there the Microsoft troubles as well. Hope it will get resolved soon.

Conclusion

Huh, that was quite a journey! With all the time spent, I am pretty happy with the result and hope this article will save someone’s time.

Astro is a great piece of technology that is easy to learn and easy to use, and its ecosystem will only expand with its popularity. At some point, we’ll likely have an @astrojs/comments solution, reducing this effort to simply running npm install and editing tailwind.config.mjs. In the meanwhile, Remark42 is one of the possible commenting solutions for Astro websites.