Most group-coordination tools start the same way: download the app, or create an account, or both. We wanted the opposite. With CollectiveTap, the unit of software is the link. You create one, paste it into a group chat, and it's a live poll, RSVP, signup sheet, or expense split — for everyone, with zero sign-up on either side.
Here's the architecture that makes that possible, and the three decisions that mattered most.
Decision 1: No accounts — authorization lives in the URL
There are no users in our database, because there are no user accounts. Every link carries a signed capability token that encodes what the holder is allowed to do:
// Simplified: a capability is an HMAC-signed payload in the URL
// { id, scope, exp, v } — scope: "rw" (participate) or "owner" (admin)
function verifyCap(token, link) {
const { body, sig } = split(token);
if (!timingSafeEqual(sig, hmac(body, SIGNING_KEY))) return null; // forged
const cap = JSON.parse(body);
if (cap.v !== link.cap_version) return null; // rotated → revoked
if (cap.exp < now()) return null; // expired
return cap;
}
Rotating a single cap_version integer on the link invalidates every token ever issued for it — bulk revocation without a sessions table. The token is clamped so it can never outlive the link itself. The entire authorization model is a function and a secret. No auth provider, no session store.
Decision 2: No framework — one source of visual truth
There's no React, no bundler, not even a build step for the front end beyond concatenation. The server renders authoritative HTML; browser JS is purely additive and fail-silent (it may never throw or block rendering).
The non-obvious win is that each tool has exactly one SVG layout module, and that same module renders four surfaces:
- the Open Graph preview card (server, SVG→PNG),
- the creator's draft preview (browser),
- the admin preview,
- the live participant page.
One layout core → four surfaces means a preview can never "drift" between what you see while creating a link and what your friends see in iMessage. If you've ever shipped a share feature where the preview card looked different from the real page, you know exactly why this matters. (You can see the live tools here: CollectiveTap tools.)
Decision 3: Preview cards as immutable, edge-cached artifacts
The hardest part of a share-native product is that the Open Graph image must be correct and fast the instant a link lands in a chat app — and it must update when the underlying data changes, without ever serving something stale.
We render each card as SVG and rasterize to PNG with Resvg inside a Netlify Function, then serve it from a content-addressed, versioned path:
/og/i/{id}/{preview_version}.png
Every state mutation bumps preview_version, which changes the URL, which means the old image can be cached forever and the new one is fetched on next share. Supabase Storage holds the cache; Netlify Edge serves the bytes. Immutable URLs + a version counter is a tiny pattern that eliminates a whole class of stale-preview bugs.
The stack, in one breath
- Vanilla JS front end, no framework, no bundler.
- Netlify Functions + Edge for all serving, routing, and caching.
- Supabase Postgres for link state and an append-only event spine; Supabase Storage for the preview-image cache.
- Resvg for SVG→PNG.
That's the whole thing. A small team can run a product that feels like real-time infrastructure because we pushed almost everything to the edge and kept the data model boringly simple.
Was it worth dropping accounts and frameworks?
For this product, unambiguously yes. The "share a link, it just works" experience is the entire value proposition, and accounts or installs would have killed it. If you're building something share-native — a poll, a signup, a one-off coordination tool — consider whether your unit of software can be the link itself.
If you want to see it in practice, here are a few real flows people use it for: group coordination use-cases.
Building CollectiveTap — group tools that live inside a link. No account, no install.
Top comments (0)