I like static websites for the same reasons most people do: they're simple, fast, easy to deploy, and there is very little infrastructure to maintain.
Then you want comments.
Suddenly, the simple static site needs a third-party comment platform, a separate backend, or a server/database that you now have to maintain.
I didn't really like any of those tradeoffs, so I built StaticLayer.
StaticLayer adds moderated comments, anonymous reactions and polls to static websites, with the dynamic part deployed into the site's own Cloudflare account.
Website: https://abla25.github.io/StaticLayer/
The idea
The basic architecture is deliberately small:
Static site
│
▼
StaticLayer widget
│
▼
Cloudflare Worker
│
▼
Cloudflare D1
There is no StaticLayer-hosted comment database in the middle.
The Worker, D1 database and secrets belong to the site owner's Cloudflare account.
That was the main design constraint I wanted to explore:
Can a static website get useful interactive features without becoming dependent on a centralized SaaS backend?
Comments
The main feature is a moderated comment system.
Comments are plain text only. They can have nested replies up to three levels, likes, pinning, reporting, and newest/oldest/best sorting.
Every new comment enters a moderation queue.
The owner can then approve, delete or reply from the admin interface.
The same moderation queue works across all the pages using StaticLayer.
There is no visitor account or email requirement.
A visitor can simply choose a nickname and write a comment.
But what about spam?
This was one of the more interesting parts to build.
I didn't want to add a CAPTCHA to a comment form.
Instead, a visitor first requests a signed, single-use challenge.
The browser solves a small Proof-of-Work challenge and sends the result with the comment.
The Worker verifies the signature and Proof-of-Work, consumes the challenge atomically, and only then stores the comment as pending.
There is also a honeypot and a three-second time gate.
So the basic flow is:
Browser
│
│ GET challenge
▼
Worker
│
│ signed single-use challenge
▼
Browser
│
│ solve PoW
│
│ POST comment + proof
▼
Worker
│
├── verify signature
├── verify PoW
├── consume challenge atomically
└── store as pending
│
▼
D1
│
▼
Admin approval
│
▼
Published
The goal isn't to claim that spam is impossible.
The idea is simply to make automated abuse expensive without requiring the reader to identify themselves.
Privacy by default
The public widget doesn't use tracking cookies, fingerprinting or analytics, and the runtime doesn't persist visitor IP addresses.
Comments are plain text and rendered as text rather than arbitrary HTML.
Reactions work similarly.
A visitor can click an emoji without creating an account or identifying themselves.
Each reaction uses a Proof-of-Work challenge, with increasing difficulty as an article receives more reactions.
The API exposes aggregate counts rather than per-visitor identity.
There is an important tradeoff here: anonymous anti-abuse mechanisms cannot know that two requests came from the same human. The system therefore uses cost rather than identity.
Reactions
Reactions are intentionally much simpler than comments.
For example:
👍 38 ❤️ 21 🎉 9
You can place the reaction bar together with comments, use it without comments, or put it anywhere on the page.
The emoji set is configurable.
The interesting part is again the anti-abuse model: each reaction requires a small Proof-of-Work, and the difficulty can escalate as the article becomes more popular.
Polls
The third component is a StrawPoll-style poll.
Polls can be single-select or multi-select and can be embedded anywhere on a site.
Results are ranked, and a poll can also be embedded globally across pages.
There is an optional one-vote-per-browser guard. It is intentionally anonymous: the server stores a hash of an anonymous browser token rather than an identity.
Like the reaction system, it is not pretending that anonymous voting can provide perfect “one human, one vote” guarantees.
Installation
I wanted installation to be boring.
There are three options.
Browser installer
A hosted browser installer can deploy the Worker, D1 database and secrets into the user's Cloudflare account.
CLI
npx staticlayer init
The CLI observes, plans, applies and verifies the deployment.
Manual
For people who want complete control, everything can also be created manually through the Cloudflare dashboard.
Once deployed, the site only needs the widget:
<div
data-staticlayer
data-api="https://comments.example.com">
</div>
<script
src="https://comments.example.com/widget.js"
defer>
</script>
The same snippet can be used across pages. By default, the page URL identifies the comment thread.
No build-time integration is required.
If the site can embed JavaScript, it can use StaticLayer.
Static sites it can work with
The widget is framework-agnostic.
I've documented integrations for:
- plain HTML
- Astro
- Hugo
- Jekyll
- Next.js
- GitHub Pages
- Cloudflare Pages
The important requirement is simply that the page can load the JavaScript widget.
Why Cloudflare?
This is probably the part I'm most interested in discussing.
Cloudflare already provides the primitives needed for this architecture:
- Workers for the runtime
- D1 for the SQL database
- secrets for private configuration
In fact, Cloudflare's own documentation now has a tutorial called “Build a Comments API” that demonstrates using Workers and D1 to add comments to a static blog.
StaticLayer is basically taking that idea one step further into a reusable component: instead of building a comments API for one particular blog, the deployment creates the entire comment layer in the site owner's own Cloudflare account.
The tradeoff
This obviously isn't for everyone.
You need a Cloudflare account.
And if you don't want to use Cloudflare at all, StaticLayer isn't the right solution.
But for someone already using Cloudflare, the architecture is attractive to me:
Your static site
+
Your Worker
+
Your D1
=
Your comment system
There is no central StaticLayer database storing everybody's comments.
Why I built it
I'm not trying to replace every existing comment system.
Giscus makes sense if GitHub Discussions are what you want.
A traditional self-hosted solution makes sense if you want to run your own server.
A hosted comment SaaS makes sense if you don't want to manage infrastructure.
StaticLayer is for a slightly different preference:
I want my static site to stay static, but I also want a small dynamic layer — and I want that layer to live in infrastructure I control.
That's the experiment.
I'd love feedback from people running Hugo, Astro, Jekyll, GitHub Pages or other static sites:
How are you currently handling comments, reactions or polls, and what do you dislike about your current solution?
StaticLayer:
https://abla25.github.io/StaticLayer/
Interactive demo:
https://abla25.github.io/StaticLayer/demo.html
Installation guide:
https://abla25.github.io/StaticLayer/install.html

Top comments (0)