Static site generators like Astro deliver blazing-fast performance, but integrating user comments without heavy infrastructure can feel daunting. Traditional solutions—Disqus, Commento, or even custom backends—introduce complexity, external dependencies, or unwanted scripts. A simpler path exists for sites hosted on Netlify: leveraging Netlify Forms and lightweight serverless functions to build a fully controlled, low-maintenance comment system.
This approach mirrors the minimalist philosophy of Jamstack—using tools you already rely on to solve a common need. The result? A comment system that doesn’t compromise speed, security, or editorial control. Let’s walk through how it works and how to implement it on your own Astro blog.
A four-step workflow for secure comment approval
The system relies on Netlify’s built-in form handling and webhooks to automate comment moderation without manual backend setup. The process unfolds in four steps:
- A visitor submits a comment via a standard HTML form. Netlify captures the POST request and stores it in a queue labeled
blog-comments, bypassing your origin server entirely.
- A webhook fires immediately after submission, triggering a serverless function called
comment-handler. This function generates an email containing two secure, time-limited links—one to approve the comment, another to delete it.
- When you click Approve, the link calls
approve-comment, which copies the data into a second form namedapproved-commentsand removes it from the queue. The original submission date is preserved for accurate display.
- A third function,
get-comments, fetches only from theapproved-commentsform. Readers never see unapproved content, and no raw email addresses or personal data are exposed in the browser.
This design eliminates databases, dashboards, and third-party scripts. Comments go live only after explicit approval from your inbox—and the entire flow runs on Netlify’s infrastructure.
Building the comment form in Astro
Netlify Forms works by scanning static HTML for forms marked with data-netlify="true". In an Astro project, this means using a server-rendered .astro component so the form appears in the final HTML and is registered automatically on first deploy.
---
const postId = Astro.url.pathname.split('/').pop();
---
<form name="blog-comments" method="POST" data-netlify="true" netlify-honeypot="bot-field">
<input type="hidden" name="form-name" value="blog-comments" />
<input type="hidden" name="postSlug" value={postId} />
<!-- Honeypot field for spam protection -->
<input name="bot-field" style="display:none" tabindex="-1" />
<!-- Visible fields -->
<label>
Name:
<input type="text" name="name" required />
</label>
<label>
Email (optional):
<input type="email" name="email" />
</label>
<label>
Comment:
<textarea name="comment" required></textarea>
</label>
<button type="submit">Submit</button>
</form>Key points in the form structure:
- The
form-namefield ensures Netlify routes submissions correctly, especially when using JavaScript-based submission viafetch.
- The
postSlugfield associates each comment with a specific post. It’s used later to filter comments by URL path.
- The hidden
bot-fieldacts as a honeypot. Bots typically fill every field, while real users ignore it. Netlify silently discards submissions that trigger it.
The form submits via fetch with Content-Type: application/x-www-form-urlencoded, and Netlify intercepts these requests before they reach your server.
Serverless functions: handling, approving, and retrieving comments
Three lightweight Netlify Functions work together to process, moderate, and serve comments.
Function 1: get-comments – fetching approved comments
The get-comments function reads from the approved-comments form using the Netlify API and filters results by postSlug. It ensures privacy by hashing email addresses server-side before sending them to the frontend.
// netlify/functions/get-comments.js
import crypto from "node:crypto";
function gravatarHash(email) {
return crypto
.createHash("md5")
.update(email.trim().toLowerCase())
.digest("hex");
}
const comments = submissions
.filter((s) => s.data?.postSlug === slug)
.map((s) => ({
name: s.data.name,
comment: s.data.comment,
date: s.data.originalDate || s.created_at,
emailHash: s.data.email ? gravatarHash(s.data.email) : null,
}))
.sort((a, b) => new Date(a.date) - new Date(b.date));The function uses originalDate instead of created_at to preserve the timestamp when the visitor submitted the comment, not when it was approved. The originalDate value is stamped during the approval step. All secrets live in environment variables, and the function returns an empty array in local development to prevent errors.
Function 2: comment-handler – triggering moderation emails
When a new submission lands in blog-comments, a Netlify outgoing webhook triggers comment-handler, which sends a moderation email via Resend. Each email includes two HMAC-SHA256-signed action links—one for approval, one for deletion.
// netlify/functions/comment-handler.js
import crypto from "node:crypto";
function hmac(submissionId, action, secret) {
return crypto
.createHmac("sha256", secret)
.update(`${submissionId}:${action}`)
.digest("hex");
}
const approveToken = hmac(id, "approve", secret);
const deleteToken = hmac(id, "delete", secret);Each token encodes the submission ID and intended action. This prevents token reuse across submissions and actions, enhancing security. The links point to the approve-comment function and include the token as a query parameter.
Function 3: approve-comment – re-posting and archiving
The approve-comment function validates the HMAC token using crypto.timingSafeEqual to prevent timing attacks, then re-submits the comment data to the approved-comments form via Netlify’s API. It also sets originalDate to the original submission timestamp before the comment is marked as approved.
With these three functions in place, your Astro blog gains a fully functional comment system that stays fast, private, and under your control—all without a database or external service.
Looking ahead: a sustainable comment model for static sites
Static sites prioritize speed and simplicity, but user engagement often demands comments. This Netlify-based approach delivers both: a near-zero-maintenance comment system that respects performance and editorial autonomy.
As Jamstack ecosystems evolve, tools like Netlify Forms and serverless functions continue to blur the line between static and dynamic capabilities. For bloggers and small teams, that means more features without the overhead. The system outlined here can scale from personal blogs to small communities—all while keeping your site lean, fast, and secure.
AI summary
Learn how to implement a self-hosted comment system for Astro using only Netlify Forms and serverless functions—no database, no external scripts, and full control.