Commenting now works with two redirects
This blog is static HTML using hugo. There’s no comment backend, no database, no JavaScript framework. And yet under this post there’s a place to submit a comment. And under every comment there’s a link to reply if you want, and if you do, the blog navigates briefly to popsicleboat and returns to this page, scrolled to a bar that tells you what’s going on with a spinner. Then the page automatically refreshes when it’s ready, showing what you wrote. It takes about a minute because the hugo site needs to rebuild.
Last time I wrote about the author side: write, run a command to link your post to popsicleboat to enable comments hosted there, and push. Things have changed a bit since then. This is to describe the reader side and how comments work. I made it more frictionless and that took more machinery. Here’s the whole loop.
And things continue to change, this project is very much alive. If you’re reading this blog keep in mind older posts describing how things work are probably a touch out of date.
What submit actually does #
The reply box is a plain HTML form. No javascript attached. Submitting it POSTs text across the internet to Popsicle Boat’s endpoint.
That POST carries no cookies. Browsers stopped sending them on cross-site form posts years ago, a security design against CSRFs. Because the request carries no authority, the endpoint can’t post anything on your behalf. All it does is park your draft server-side under a random token, then answer with a redirect to the thread, token in hand. The redirect is a 303, which turns the POST into an ordinary top-level GET, and on an ordinary GET your Popsicle Boat session is back.
The moment you land on Popsicle Boat, three checks run: 1 - you’re signed in
2 - the draft is recent (to verify it is the post just made)
3 - you arrived from the same internet address the draft was submitted from.
The idea is that alone, each of these checks would be leaky. Together they assert this is the comment from the same person at the same moment. This prevents abuse.
All three pass and the reply posts on the spot, through the same pipeline as the boat’s own composer, so rate limits and moderation apply the same. No second button, no form over there, nothing to confirm. From your side it was simple like any other commenting mechanism: 1 - type
2 - submit
and you’re already on your way back to where you clicked submit.
The only caveat for this particular blog is it does need to rebuild since comments and replies are static, as is the whole blog. The good news is it’s pretty easy to configure that because it’s hosted on netlify and setting the build hook is a one time process. Most popular hosting sites offer something similar.
The build hook #
I’ve written about it before but since it’s context here it’s worth a refresher.
For this blog the hook lives in the Netlify dashboard under Site configuration → Build & deploy → Build hooks. Click “Add build hook,” give it a name, and Netlify gives you a unique URL. Anything that sends an empty POST to that URL triggers a fresh build and deploy of the site. That URL is what you paste into your Popsicle Boat account settings (the build-hook field), and it’s what Popsicle Boat pings the moment a new reply posts, triggering the build.
Everything after the trigger is just information for the reader: a status bar with a spinner sitting where the comment will appear.
When it asks first #
The checks exist for cases where posting silently would be wrong, and every miss falls back to a default. Basically the way it was before. The composer open, with your draft already in it, and one button.
The address check matters because the landing URL is shareable. Popsicle Boat notes the IP address that submits a draft, and it auto-posts only when the browser that opens the landing link is at that same address. Without it, sending that link to a friend would post your words under their name the moment they opened it. And if you’re signed out, you sign in (or make an account) first; the draft survives the detour, waiting up to half an hour, enough to confirm an email and still find your words in the box. The draft is only consumed at the moment of posting, so nothing you typed disappears.
The reason I call it an “arrival” rather than “your return” is that the landing URL is just a URL. Whoever opens it, whether it’s you, or a friend you pasted it to, or a browser restoring a tab arrives with the token… Popsicle Boat can’t tell from the token alone who’s holding it. So it compares the arriving browser’s IP address to the one that submitted the draft, and only auto-posts when they match.
The trip back #
After the reply posts, a toast briefly appears from the top saying it’ll show up on the blog in a few minutes and that you’re being sent back. A couple of seconds later you’re on the originating page again. Not at the top of it, the return URL carries a fragment that steers the scroll to the place where there’s a placeholder note with a small spinner and a basic notice that your comment will appear shortly, sitting exactly where your comment is about to appear.
Popsicle Boat only honors that return URL because this blog is the verified website on my profile there. A tampered return address gets dropped, so the endpoint can’t be used as a redirect for anywhere else. That’s important, I’ve written about it before but if you want to use Popsicle Boat there is some configuration.
And as always, I am personally reachable if you have questions.
The page finishes the job #
Posting also pinged my build hook, immediately. The pinger fires on the leading edge and then holds a window, so one reply means one instant rebuild and a burst of replies means two, not a dozen. Netlify rebuilds, Hugo fetches the thread’s replies.json, and the comment becomes static paragraphs in the HTML like every other one. If Popsicle Boat goes down or just goes away for any reason, those comments still live on the site where they were made.
Meanwhile the note you landed on is doing the only JavaScript in the whole loop: every ten seconds it checks a tiny build-stamp file. When the stamp changes, the page reloads itself, scrolls to your new comment, and puts a highlight on it. The script is a couple of kilobytes served from this site, and it only runs when you arrive with the fragment. A plain visit to the page runs nothing. Note: This is not required for integration with Popsicle Boat. The script is specific to jva.lol.
Threads that stay readable #
Every top level comment and reply can be replied to in a basically unlimited nesting structure. Indentation steps in for three levels and then stops; deeper runs fold behind a, for example, “7 more replies” link, and every three levels inside a fold it folds again. Inside each fold the indent restarts from the edge, so a parent and its child always read as parent and child instead of drifting off the right side of a phone or tablet, or any device. The idea is that it’s adaptive and never loses control or context from where you are.
What your browser never does here #
No cookies from this site. No third-party script, no iframe, no tracker. Reading the conversation is reading HTML that was baked at build time, and this site keeps its own copy of every reply. Writing is one form and two redirects: a POST that parks a draft, a 303 to where your account lives, and a redirect home.
The author-side setup is unchanged from the earlier post, and the general arrangement is documented at popsicleboat.com/comments.
This page is the demo. The box below is that form.