Insights / Technical notes

Build Astro Blog Comments with Cloudflare Only

How we added comments to an Astro blog without an external comment service, using only Cloudflare Pages Functions, D1, Turnstile, and Wrangler configuration.

  • Technology
  • Cloudflare
  • Astro
  • Security
  • Website
Build Astro Blog Comments with Cloudflare Only
Table of contents
  1. Architecture
  2. Why D1
  3. Wrangler As The Contract
  4. Turnstile Must Be Verified Server-Side
  5. Spam Controls
  6. SEO And Search
  7. Summary
  8. References

Static sites usually avoid server-side state. Comments are the moment that rule becomes inconvenient.

For Acecore, we did not add an external comment SaaS or embedded widget. The implementation in PR #101 keeps the whole feature inside Cloudflare:

  • Astro renders the comment UI.
  • Cloudflare Pages Functions expose /api/comments.
  • Cloudflare D1 stores comments.
  • Cloudflare Turnstile protects POST requests.
  • wrangler.jsonc defines the COMMENTS_DB binding.

That is the main point: the comment feature is not a third-party island inside the page. It uses the same Cloudflare boundary as the rest of the static site.

Architecture

The implementation has a small surface area.

Layer File or service
UI src/components/BlogComments.astro
Page placement src/views/BlogPostPage.astro
API functions/api/comments.ts
Storage D1 binding COMMENTS_DB
Bot protection Cloudflare Turnstile
Schema migrations/0001_create_blog_comments.sql

The UI fetches comments with GET /api/comments?slug=...&locale=... and submits with POST /api/comments.

The Pages Function validates origin, payload, Turnstile, rate limits, duplicates, and blocked content before inserting into D1.

Why D1

Comments are relational enough to benefit from SQL:

  • select by post_slug
  • order by created_at
  • hide rows with deleted_at
  • count recent rows by client_hash
  • detect duplicate body_hash values

The schema stores a soft delete timestamp instead of physically removing rows. That keeps moderation simple: visible comments are rows where deleted_at IS NULL.

Cloudflare D1 supports prepared statements from Workers and Pages Functions. Using prepare(...).bind(...) keeps comment input out of raw SQL strings.

Wrangler As The Contract

Bindings are defined in wrangler.jsonc.

{
  "d1_databases": [
    {
      "binding": "COMMENTS_DB",
      "database_name": "acecore-comments",
    },
  ],
  "env": {
    "production": {
      "d1_databases": [
        {
          "binding": "COMMENTS_DB",
          "database_name": "acecore-comments",
        },
      ],
    },
  },
}

The important part is keeping the binding name stable while pointing every environment at the single D1 database, acecore-comments.

Cloudflare Pages documentation treats Wrangler configuration as the source of truth for a Pages project, which is exactly what we want for reviewable infrastructure changes.

Turnstile Must Be Verified Server-Side

The widget in the browser is only the beginning. The Pages Function sends the token to Cloudflare Siteverify with the secret key.

It also checks the hostname returned by the verification result. This matters for preview URLs and for preventing tokens from an unexpected hostname from being accepted.

Spam Controls

The first version is intentionally strict.

It rejects:

  • URLs
  • email addresses
  • HTML tags
  • Markdown links
  • repeated-character spam
  • common promotional terms
  • honeypot field submissions

It also applies in-memory rate limits and persistent D1-backed limits. The persistent limit uses a salted hash of IP and User-Agent instead of storing raw values.

The comment UI is marked with data-pagefind-ignore, and comments are loaded client-side. That means comments are not treated as indexed article content.

For a corporate blog, that is intentional. The article body is reviewed content; comments are interaction. If comments should become searchable content, they need approval and static rendering as a separate design.

Summary

External comment services are convenient, but they are not the only option.

If a site already runs on Cloudflare Pages, a lightweight comment feature can live entirely inside Cloudflare: Pages Functions for the API, D1 for storage, Turnstile for abuse prevention, and Wrangler for bindings.

That keeps UI, data, security boundaries, and infrastructure configuration under the same operational model as the site itself.

References

Cloudflare Comments

Architecture for a comment feature built only with Cloudflare

Astro renders the UI, Cloudflare Pages Functions form the API boundary, and D1 and Turnstile are connected as Cloudflare components.

  1. Place the UI in Astro

    Place the comment list, submission form, and Turnstile widget below each article.

  2. Receive requests in a Pages Function

    `/api/comments` handles GET/POST/OPTIONS, input validation, and CORS.

  3. Store data in D1

    Use the `COMMENTS_DB` binding to store comments, hashes, and creation times in SQLite-compatible D1.

  4. Protect it with Turnstile

    Validate Cloudflare Turnstile tokens server-side and check the hostname allowlist.

Differences between an external comment service and an in-house Cloudflare implementation

External comment service

  • Fast to adopt, but the UI, data location, terms, and rendering speed depend on the service
  • External scripts and iframes can easily affect article loading
  • Multilingual UI and visual consistency with the site are often constrained
  • Comment handling, deletion, and migration depend on the service specification

Built only with Cloudflare

  • Pages Functions, D1, and Turnstile provide both the API and storage
  • Astro HTML and CSS can fit naturally into the site design
  • Wrangler configuration can align the D1 binding with the Cloudflare database name
  • You decide the spam controls, deletion process, and scope of stored personal information

Decisions to make before implementation

  • Keep comments in the site's Cloudflare architecture instead of entrusting them to an external service
  • Use D1 for storage and Cloudflare Pages Functions as the API boundary
  • Always validate Turnstile tokens server-side
  • Reject URLs, email addresses, HTML, Markdown links, and promotional wording before submission
  • Align the D1 database name and COMMENTS_DB binding with the Cloudflare configuration

FAQ

Why not use an external comment widget?

External widgets are quick, but UI, data ownership, script loading, moderation, and migration all depend on the service. This implementation keeps those decisions inside the site.

Is D1 enough for comments?

For post_slug based reads, created_at ordering, duplicate checks, and soft deletion, D1 is a good fit. Larger community features need a broader design.

Is client-side Turnstile enough?

No. The Pages Function must verify the Turnstile token with Cloudflare Siteverify before writing to D1.