Rolling my own bubbles.town widget
4 min read
Andreas blogged about adding the bubbles.town widget to his blog. He used the vote widget provided by bubbles.town, which is just a bit of JavaScript you put on your pages. It's well documented and dead simple, so if you just want this on your site with no fuss, that's the way to go.
I use 11ty as my blogging platform and I could just use the same JavaScript snippet on my site but I wanted to make something that's a bit more in the spirit of a static site. It's a tiny script, so yeah, I'm being a bit of a purist about it. But I like keeping my pages free of other people's JavaScript and the third-party requests that come along with it. Plus, I wanted to style the widget a bit differently than their widget does. Here is how I did that.
What the widget actually does #
First I poked at what the widget actually does. I'm no JavaScript wizard, but it didn't take much. When the page loads, the script calls a small API on bubbles.town, GET /api/vote-count?url=..., gets back the vote count for that URL, draws it on the page, and links out to bubbles.town so you can go vote.
That last bit is the important part. The voting never happens on my site. It's just a link over to bubbles. The JavaScript is really only there to paint a number.
And a number I can fetch myself.
Fetching it at build time #
So instead of shipping a script that makes every visitor's browser phone bubbles.town on every page load, I call that same endpoint once, when my site builds. 11ty makes this easy. I added a little computed field to my posts that hits the API:
async function bubblesSignal(url) {
const res = await fetch(
`https://bubbles.town/api/vote-count?url=${encodeURIComponent(url)}`
);
if (!res.ok) return null; // not on bubbles
const { id, count } = await res.json();
return { id, count };
}
Then I render the result straight into the page like any other bit of data. No <script>, no third-party request when you load the post, just a plain link with the count baked right in:
{% if bubblesSignal %}
<a class="bubbles-vote" href="https://bubbles.town/entry/{{ bubblesSignal.id }}">
▲ {{ bubblesSignal.count }} on bubbles
</a>
{% endif %}
It sits right next to the likes and reposts I already pull in from Mastodon and Bluesky (those are static too, but that's a post for another day). And because it's my own little chunk of markup, I got to style it however I liked. A small amber triangle, my own fonts, and it picks up dark mode for free. Nice.
The one annoying wrinkle #
There was one snag. bubbles only knows about posts it's imported from your RSS feed, so it returns a 404 for anything it hasn't seen. My build runs every few minutes and wipes its cache each time, so my first lazy version re-hit bubbles for every post on every build. Rude. Sorry, bubbles.
My first fix was a little cache that remembered the misses too, so a 404 wouldn't get re-checked for a day. It worked, but it had a dumb side effect: a brand-new post would get stamped "not on bubbles" and then sit there for a whole day before its count showed up, even after bubbles had actually imported it.
So I looked at which posts were the permanent misses, and it was obvious in hindsight. They were all my reply posts, the little "Re:" ones I send off as webmentions. Those don't go in my main RSS feed, so bubbles never sees them, so they'll never have a count. No point ever asking. (Anything from before I joined bubbles is the same deal, it was never going to be indexed, so those get filtered out by date up front.)
So now I just skip those by rule and let every real post fetch live. A new post picks up its count on the next build after bubbles imports it, no babysitting required. The cache went from "a clever pile of 404s" down to "the counts I've already looked up." Good enough is good enough.
Conclusion #
So that's it. The same little upvote counter Andreas added, but with no JavaScript shipped to my readers, no third-party request when you load a post, and styled to match the rest of my site. I get the signal, bubbles still gets the vote, and my pages stay nice and quiet.
If you're running a static site and you're on bubbles.town, are you using their widget, or did you roll your own? I'd love to see how you did it.
Leave a comment