The URL is a database you already have
Diorama, our new Lab room, runs on GitHub Pages. Static hosting, no backend, no database. And yet you can arrange a room and send someone a link that opens your exact room — no account, no server, no saved record anywhere. The trick is old and underused: put the whole state in the URL.
The hash is free storage
Everything after the # in a URL — the fragment — never travels to the server. It lives entirely in the browser. That makes it the perfect place to stash application state you want to travel with a link but never persist server-side.
Diorama's state is small and structured: a list of furniture (type, position, rotation, colour) and the room settings (walls, floor, rug, lamps). We serialise that to JSON, base64-encode it, and drop it in the hash — #s=eyJsYXlvdXQ…. On load, if that parameter is present, we decode it and rebuild the room before anything else runs. A shared link wins over local storage; local storage wins over the default room.
const encode = p => btoa(unescape(encodeURIComponent(JSON.stringify(p))));
const url = location.origin + location.pathname + '#s=' + encodeURIComponent(encode(state));
That is the entire "backend". Copy the string, paste the link, and the receiver's browser reconstructs the scene deterministically — the same data always produces the same room.
Why not just use a server?
Because for a lot of things you do not need one, and every server you add is something to host, secure, back up and pay for. If the state is small, structured, and only meaningful to the person who made it, the URL is a database you already have — globally distributed, free, and needing no auth.
Where it stops working
The pattern has honest limits, and pretending otherwise is how people get burned:
- Length. URLs realistically top out around 2,000 characters before some client chokes. Fine for a room; useless for a document. Large state needs real storage.
- No single source of truth. Every link is a frozen snapshot. There is no "latest version", no collaboration, no analytics on what people made. A feature for a toy; a dealbreaker for a product.
- Versioning. The moment your schema changes, old links must still decode — so tag the payload with a version and keep the decoder backwards-compatible, or accept that old links break.
- It is public. Anything in a URL can be logged, shared and cached. Never put anything private in there.
The other half: an image
A link rebuilds the interactive scene, but people also just want a picture to post. So Diorama has a second share path: render the current frame to a PNG with canvas.toDataURL() (with preserveDrawingBuffer enabled) and trigger a download. A link for "come play with mine", an image for "look what I made". Between the two, there is no server at all.
Play with it on the live Diorama, or read the source on GitHub.