I really wanted an easy way to add comments to my blog. After seeing how it was done on be-far, I thought about trying something similar.

Research

I began by researching the best options available on the market. My findings include:

After thorough investigation, I ultimately chose coralproject as it appears to be the most robust solution. Moreover, I was eager to learn something scalable for both educational purposes and future use. A notable limitation is its lack of support for anonymous user comments—users must create an account to participate. Despite this, and being aware of the significant spam challenges mentioned by others, I concluded that this drawback was manageable.

Each of these projects has its advantages and disadvantages. However, it’s worth noting that none of them offers a straightforward setup process on render. With that in mind, let’s explore this aspect further.

Implementation

Docker

I aim to maintain this project as a monorepo so similar to the approach I previously described for umami described in the domain-analytics article I’ve created:

comments/Dockerfile
FROM coralproject/talk:9.0.1

And integrate it by

render.yaml
services:
...
  - type: redis
    name: cache
    ipAllowList: []
    plan: free
  - type: web
    name: "talk"
    runtime: docker
    rootDir: "./comments"
    plan: free
    envVars:
      - key: MONGODB_URI
        sync: false
      - key: REDIS_URI
        fromService:
          type: redis
          name: cache
          property: connectionString
      - key: SIGNING_SECRET
        generateValue: true
      - key: PORT
        value: 5000
databases:
  - name: umami-db
    plan: free
    databaseName: umami
    user: umami

Database

I’ve chosen to utilize atlas product from mongodb , even though hosting MongoDB directly through render (https://docs.render.com/deploy-mongodb) is an option, the detailed information on backups

”Relying on a disk snapshot to restore a database is not recommended. Restoring a disk snapshot will likely result in corrupted or lost database data. Using a database’s recommended backup tool (for example: mongodump) is the recommended way to backup and restore a database without corrupted or lost data.”

convinced me of the benefits of opting for a third-party solution for enhanced robustness.

SMTP

After a brief exploration on Reddit, I was attracted to the generous offerings from smtp2go. Unfortunately, I encountered login issues with my email, which may have been due to my recent setup of email-routing for cloudflare. Despite waiting for about 30 minutes, I had no success. Subsequently, I discovered resend and successfully integrated it with ease, thanks to their straightforward guide at https://resend.com/docs/dashboard/domains/cloudflare.

Frontend integration

Although it took longer than expected, I managed to achieve ‘good enough’ results with wiring

quartz/components/scripts/coralTalk.inline.ts
if (window.location.hostname !== 'localhost') {
    window.addEventListener('CoralReady', () => {
        window.Coral.createStreamEmbed({
            id: "coralTalk",
            autoRender: true,
            rootURL: 'your-root-url',
            storyID: window.document.querySelector('meta[property="story-id"]')?.getAttribute('content') ?? window.location.pathname,
        });
 
        document.addEventListener('nav', () => {
            window.Coral.createStreamEmbed({
                id: "coralTalk",
                autoRender: true,
                rootURL: 'your-root-rul',
                storyID: window.document.querySelector('meta[property="story-id"]')?.getAttribute('content') ?? window.location.pathname,
            });
        })
    })
 
    var d = document, s = d.createElement('script');
    s.src = 'https://your-root-url/assets/js/embed.js';
    s.async = false;
    s.defer = true;
    s.onload = function () {
        window.dispatchEvent(new Event('CoralReady'));
    };
    (d.head || d.body).appendChild(s);
}

into

quartz/components/Footer.tsx
...
import coralTalk from './scripts/coralTalk.inline';
...
        <div id="coralTalk" />
...
  Footer.afterDOMLoaded = coralTalk;
...

Using this solution, loading comments generally (😉) functions well, even in Single Page Application mode .

One important aspect to highlight is that I utilize a meta story-id when available. This approach allows me to shuffle URLs while avoiding the direct use of comment links created by coralproject. Implementing this also necessitates a change in

quartz/components/Head.tsx
<head>
...
	{fileData.frontmatter?.author && <meta property="article:author" content={fileData.frontmatter.author} />}
    {fileData.frontmatter?.storyId && <meta property="story-id" content={fileData.frontmatter.storyId} />}
...
</head>

Be aware that in the current version of coralproject, the meta property author does not function as expected 🤷‍♂️. However, article:author is parsed correctly and can be seen in the coralproject admin panel 😎.

Customization

The default view provided by coralproject didn’t mesh well with the default theme of quartz. The solution can be found at https://docs.coralproject.net/css, which essentially involves adding a link to custom CSS in the configuration. This approach is how the styling was achieved here.

quartz/static/coral.css
#coral {
    --palette-primary-900: #e5eaee;
    --palette-primary-800: #cad5dd;
    --palette-primary-700: #b0c1cc;
    --palette-primary-600: #95acbb;
    --palette-primary-500: var(--secondary);
    --palette-primary-400: #627988;
    --palette-primary-300: #4a5b66;
    --palette-primary-200: #313c44;
    --palette-primary-100: #191e22;
    --palette-background-body: var(--light);
    --palette-text-000: #FFFFFF;
    --palette-text-100: #65696B;
    --palette-text-500: var(--darkgray);
    --palette-text-900: var(--secondary);
    --palette-grey-100: #191e22;
    --palette-grey-200: #313c44;
    --palette-grey-300: #4a5b66;
    --palette-grey-400: #627988;
    --palette-grey-500: var(--secondary);
    --palette-grey-600: #95acbb;
    --palette-grey-700: #b0c1cc;
    --palette-grey-800: #cad5dd;
    --palette-grey-900: #e5eaee;
    .coral-rte-content {
        color: #7b97aa;
    }
    .coral-comment-username {
        color: var(--secondary);
    }
    .coral-comments {
        padding: 16px;
    }
}

While it’s not perfect, it’s sufficiently good for the time being.

Summary

I dedicated roughly three full days to setting this up, largely due to the scarcity of comprehensive documentation. Although coralproject isn’t optimized for SPA, I’ve managed to set everything up without incurring any infrastructure costs (yet 😉). At first glance, it seems like an easily scalable architecture that I can further develop. My hope is that this guide will enable you to get up and running in just a few hours of development. If you’ve made it this far, leave a comment, or somewhere, someday, some little rabbit will meet an untimely end 🐰 (refresh page if there is no section with comments 🤷‍♂️).