Denys Isaichenko

Software Engineer

Adding comments to Astro (SSG) website

Following the article My simple 16 step system for adding comments to my static site built with Astro, I decided to add comments to one of my pet projects on Astro.

Previously, I was using the Hyvor comments system, but it doesn’t have a free plan, and I don’t really like loading external widgets/scripts onto the website. Additionally, their script was around ~50kb, which is a lot.

To make the switch, I did the following:

Switching from Vercel to Netlify

Since I wanted to use Netlify Forms and Vercel doesn’t offer a similar feature, I switched to Netlify. The switch process was pretty straightforward, with nothing particularly special.

One note: Netlify is free for the first 100 submissions per month, which is quite enough for my project.

Exporting Comments from Hyvor and Importing to Supabase

The next step was to export the comments I already had from Hyvor and decide where to store comments after the switch.

Exporting was quite easy because they provided a backup to JSON option in the account settings.

For storing comments with the new system, I decided to go with Supabase , which was mentioned in the source article. It’s free for my purposes and super easy to use. You can just install @supabase/supabase-js and use it as a usual query builder.

I defined the database schema in the Supabase dashboard and imported existing comments using a simple Node.js script:

import type { Database } from "./database.types";
import { createClient } from "@supabase/supabase-js";
// Hyvor comments that I exported
import comments from "./comments.json";
import { stripHtml } from "string-strip-html";

const supabase = createClient<Database>(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_KEY,
);

(async () => {
  const commentsToImport = [];

  for (const comment of comments.comments) {
    commentsToImport.push({
      author: comment.user.name,
      created_at: new Date(comment.created_at * 1000).toISOString(),
      // I also stripped HTML from existing comments
      comment: stripHtml(comment.body_html).result,
      url: comment.page.url,
    });
  }

  await supabase.from("comments").insert(commentsToImport);
})();

Displaying Comments During the Build Process

Now that I had a database with comments, I was able to add it to Astro as part of the build process.

The code to fetch comments was placed in getStaticPaths so that the API is called only once, and comments are filtered for each page separately:

export async function getStaticPaths() {
  const comments = await supabase
    .from("comments")
    .select()
    .order("created_at", { ascending: false });

  // adding comments to props of the page
}

This will speed up the build process a little.

I then displayed the comments on the site using regular HTML tags and loops. Now, the comments are indexable by regular bots without needing JS engines.

Adding New Comments: The Tricky Part

Now that I had the comments and the process of displaying them, it was time to think about the process of adding new comments on the website.

As I mentioned earlier, I wanted to use Netlify Forms, and the main reason for that is that it’s super easy to use. You just add a regular form to the website, enable this feature in the Netlify Dashboard, and during the build, Netlify will modify the form by adding a hidden field. I suppose it’s placing a serverless function that’s handling POST requests to the URL, so it doesn’t modify or add any JS code to the client.

With Netlify Forms enabled, I was able to collect form submissions and receive notifications to my email.

The next step was to somehow add new comments to Supabase on submission. Luckily for me, it was also easy enough. I just needed to create a Netlify Function in the project with the filename submission-created.mts, and it’s automatically triggered once a new submission is created:

import type { Context } from "@netlify/functions";
import { createClient } from "@supabase/supabase-js";
import type { Database } from "../../lib/database.types";
import { stripHtml } from "string-strip-html";

const supabase = createClient<Database>(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY,
);

export default async (req: Request, context: Context) => {
  const body = await req.json();

  // body.payload contains form data
  await supabase.from("comments").insert({
    author: body.payload.data.author,
    comment: stripHtml(body.payload.data.comment).result,
    url: body.payload.data.url,
  });

  return new Response("success");
};

Automatic deployments

Adding comments to Supabase won’t automatically publish them on the website.

To solve this, I created another Netlify Function with a scheduler for deployment (every 12 hours):

import type { Config } from "@netlify/functions";

const BUILD_HOOK = "https://api.netlify.com/build_hooks/...";

export default async (req: Request) => {
  const { next_run } = await req.json();

  await fetch(BUILD_HOOK, {
    method: "POST",
  });

  console.log("Received event! Next invocation at:", next_run);
};

export const config: Config = {
  schedule: "0 */12 * * *",
};

It’s a background function, so it doesn’t have any accessible endpoint to trigger from the outside.

Final flow overview

So, in the end, the process is this:

It was fun to implement, and hopefully, I’ll do the same for my blog in the near future.