<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abla25</title>
    <description>The latest articles on DEV Community by Abla25 (@abla25).</description>
    <link>https://dev.to/abla25</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4106837%2F267bcc52-b0c5-4e3b-ae6e-7501a5b2876d.png</url>
      <title>DEV Community: Abla25</title>
      <link>https://dev.to/abla25</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abla25"/>
    <language>en</language>
    <item>
      <title>I wanted comments on my static site without another SaaS, so I built StaticLayer</title>
      <dc:creator>Abla25</dc:creator>
      <pubDate>Wed, 02 Sep 2026 20:55:44 +0000</pubDate>
      <link>https://dev.to/abla25/i-wanted-comments-on-my-static-site-without-another-saas-so-i-built-staticlayer-530d</link>
      <guid>https://dev.to/abla25/i-wanted-comments-on-my-static-site-without-another-saas-so-i-built-staticlayer-530d</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Then you want comments.&lt;/p&gt;

&lt;p&gt;Suddenly, the simple static site needs a third-party comment platform, a separate backend, or a server/database that you now have to maintain.&lt;/p&gt;

&lt;p&gt;I didn't really like any of those tradeoffs, so I built StaticLayer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;StaticLayer adds moderated comments, anonymous reactions and polls to static websites, with the dynamic part deployed into the site's own Cloudflare account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnh4koa4jtzjr30jrkjp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnh4koa4jtzjr30jrkjp4.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Website: &lt;em&gt;&lt;a href="https://abla25.github.io/StaticLayer/" rel="noopener noreferrer"&gt;https://abla25.github.io/StaticLayer/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;The basic architecture is deliberately small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Static site
    │
    ▼
StaticLayer widget
    │
    ▼
Cloudflare Worker
    │
    ▼
Cloudflare D1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no StaticLayer-hosted comment database in the middle.&lt;/p&gt;

&lt;p&gt;The Worker, D1 database and secrets belong to the site owner's Cloudflare account.&lt;/p&gt;

&lt;p&gt;That was the main design constraint I wanted to explore:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can a static website get useful interactive features without becoming dependent on a centralized SaaS backend?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Comments
&lt;/h2&gt;

&lt;p&gt;The main feature is a moderated comment system.&lt;/p&gt;

&lt;p&gt;Comments are plain text only. They can have nested replies up to three levels, likes, pinning, reporting, and newest/oldest/best sorting.&lt;/p&gt;

&lt;p&gt;Every new comment enters a moderation queue.&lt;/p&gt;

&lt;p&gt;The owner can then approve, delete or reply from the admin interface.&lt;/p&gt;

&lt;p&gt;The same moderation queue works across all the pages using StaticLayer.&lt;/p&gt;

&lt;p&gt;There is no visitor account or email requirement.&lt;/p&gt;

&lt;p&gt;A visitor can simply choose a nickname and write a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  But what about spam?
&lt;/h2&gt;

&lt;p&gt;This was one of the more interesting parts to build.&lt;/p&gt;

&lt;p&gt;I didn't want to add a CAPTCHA to a comment form.&lt;/p&gt;

&lt;p&gt;Instead, a visitor first requests a signed, single-use challenge.&lt;/p&gt;

&lt;p&gt;The browser solves a small Proof-of-Work challenge and sends the result with the comment.&lt;/p&gt;

&lt;p&gt;The Worker verifies the signature and Proof-of-Work, consumes the challenge atomically, and only then stores the comment as &lt;code&gt;pending&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is also a honeypot and a three-second time gate.&lt;/p&gt;

&lt;p&gt;So the basic flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to claim that spam is impossible.&lt;/p&gt;

&lt;p&gt;The idea is simply to make automated abuse expensive without requiring the reader to identify themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy by default
&lt;/h2&gt;

&lt;p&gt;The public widget doesn't use tracking cookies, fingerprinting or analytics, and the runtime doesn't persist visitor IP addresses.&lt;/p&gt;

&lt;p&gt;Comments are plain text and rendered as text rather than arbitrary HTML.&lt;/p&gt;

&lt;p&gt;Reactions work similarly.&lt;/p&gt;

&lt;p&gt;A visitor can click an emoji without creating an account or identifying themselves.&lt;/p&gt;

&lt;p&gt;Each reaction uses a Proof-of-Work challenge, with increasing difficulty as an article receives more reactions.&lt;/p&gt;

&lt;p&gt;The API exposes aggregate counts rather than per-visitor identity.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reactions
&lt;/h2&gt;

&lt;p&gt;Reactions are intentionally much simpler than comments.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;👍 38   ❤️ 21   🎉 9&lt;/p&gt;

&lt;p&gt;You can place the reaction bar together with comments, use it without comments, or put it anywhere on the page.&lt;/p&gt;

&lt;p&gt;The emoji set is configurable.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polls
&lt;/h2&gt;

&lt;p&gt;The third component is a StrawPoll-style poll.&lt;/p&gt;

&lt;p&gt;Polls can be single-select or multi-select and can be embedded anywhere on a site.&lt;/p&gt;

&lt;p&gt;Results are ranked, and a poll can also be embedded globally across pages.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Like the reaction system, it is not pretending that anonymous voting can provide perfect “one human, one vote” guarantees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;I wanted installation to be boring.&lt;/p&gt;

&lt;p&gt;There are three options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser installer
&lt;/h3&gt;

&lt;p&gt;A hosted browser installer can deploy the Worker, D1 database and secrets into the user's Cloudflare account.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx staticlayer init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI observes, plans, applies and verifies the deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual
&lt;/h3&gt;

&lt;p&gt;For people who want complete control, everything can also be created manually through the Cloudflare dashboard.&lt;/p&gt;

&lt;p&gt;Once deployed, the site only needs the widget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;data-staticlayer&lt;/span&gt;
  &lt;span class="na"&gt;data-api=&lt;/span&gt;&lt;span class="s"&gt;"https://comments.example.com"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://comments.example.com/widget.js"&lt;/span&gt;
  &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same snippet can be used across pages. By default, the page URL identifies the comment thread.&lt;/p&gt;

&lt;p&gt;No build-time integration is required.&lt;/p&gt;

&lt;p&gt;If the site can embed JavaScript, it can use StaticLayer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static sites it can work with
&lt;/h2&gt;

&lt;p&gt;The widget is framework-agnostic.&lt;/p&gt;

&lt;p&gt;I've documented integrations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plain HTML&lt;/li&gt;
&lt;li&gt;Astro&lt;/li&gt;
&lt;li&gt;Hugo&lt;/li&gt;
&lt;li&gt;Jekyll&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;GitHub Pages&lt;/li&gt;
&lt;li&gt;Cloudflare Pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important requirement is simply that the page can load the JavaScript widget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cloudflare?
&lt;/h2&gt;

&lt;p&gt;This is probably the part I'm most interested in discussing.&lt;/p&gt;

&lt;p&gt;Cloudflare already provides the primitives needed for this architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workers for the runtime&lt;/li&gt;
&lt;li&gt;D1 for the SQL database&lt;/li&gt;
&lt;li&gt;secrets for private configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In fact, Cloudflare's own documentation now has a tutorial called &lt;strong&gt;“Build a Comments API”&lt;/strong&gt; that demonstrates using Workers and D1 to add comments to a static blog.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoff
&lt;/h2&gt;

&lt;p&gt;This obviously isn't for everyone.&lt;/p&gt;

&lt;p&gt;You need a Cloudflare account.&lt;/p&gt;

&lt;p&gt;And if you don't want to use Cloudflare at all, StaticLayer isn't the right solution.&lt;/p&gt;

&lt;p&gt;But for someone already using Cloudflare, the architecture is attractive to me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your static site
       +
Your Worker
       +
Your D1
       =
Your comment system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no central StaticLayer database storing everybody's comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I'm not trying to replace every existing comment system.&lt;/p&gt;

&lt;p&gt;Giscus makes sense if GitHub Discussions are what you want.&lt;/p&gt;

&lt;p&gt;A traditional self-hosted solution makes sense if you want to run your own server.&lt;/p&gt;

&lt;p&gt;A hosted comment SaaS makes sense if you don't want to manage infrastructure.&lt;/p&gt;

&lt;p&gt;StaticLayer is for a slightly different preference:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;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.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the experiment.&lt;/p&gt;

&lt;p&gt;I'd love feedback from people running Hugo, Astro, Jekyll, GitHub Pages or other static sites:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How are you currently handling comments, reactions or polls, and what do you dislike about your current solution?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;StaticLayer:&lt;br&gt;
&lt;a href="https://abla25.github.io/StaticLayer/" rel="noopener noreferrer"&gt;https://abla25.github.io/StaticLayer/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interactive demo:&lt;br&gt;
&lt;a href="https://abla25.github.io/StaticLayer/demo.html" rel="noopener noreferrer"&gt;https://abla25.github.io/StaticLayer/demo.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Installation guide:&lt;br&gt;
&lt;a href="https://abla25.github.io/StaticLayer/install.html" rel="noopener noreferrer"&gt;https://abla25.github.io/StaticLayer/install.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/Abla25/StaticLayer" rel="noopener noreferrer"&gt;https://github.com/Abla25/StaticLayer&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cloudflare</category>
      <category>javascript</category>
      <category>staticweb</category>
    </item>
  </channel>
</rss>
