DEV Community

Mary Hill
Mary Hill

Posted on • Originally published at fugte.com

I put a public graffiti wall on my SaaS homepage

Somewhere below the hero, most SaaS homepages have the same section: a screenshot of the product, slightly angled, maybe in a browser mockup with fake traffic numbers. Everyone scrolls past it, because everyone knows screenshots are where marketing happens.

Our homepage section is literally titled "Proof, not promises," so a screenshot felt like self-parody. Instead we embedded the product itself: a public guestbook called Owners Were Here, built with Fugte (my widget builder), embedded on the homepage with the exact iframe snippet any customer gets. Anyone can sign it. The notes pile up into a wall. It is a graffiti wall with a submit button.

This post is the why, and then the three sharp edges you hit the moment you let strangers write to your landing page.

Why a guestbook, of all things

Two reasons, one honest and one sneaky.

The honest one: forms are the hardest widget to fake. A countdown timer in a screenshot proves nothing; anything can render a countdown. A form that visibly works proves the entire invisible half of the product: the submit endpoint, the spam screening, the storage, the fact that a random visitor on a third-party page can post data and it lands somewhere real. That invisible half is the actual product. So the demo had to be a form, and a contact form demo is boring because you can't show strangers each other's messages. A guestbook is a form whose submissions are meant to be public. The demo demos itself.

The sneaky one: a wall of real notes from real visitors is social proof that compounds while I sleep. Testimonial sections take months of collection and permission-asking. A graffiti wall fills itself, and its scrappiness is the charm. Even a wall with some junk on it says "people were here," which is more than a polished quote carousel says.

Edge 1: the endpoint is public, and there is no secret to hide

The widget is an iframe pointed at its hosted page. The instance id is right there in the URL. Anyone can read the JS. So the design constraint is brutal and clarifying:

The submit endpoint must be safe to expose to the entire internet, because it is.

There is no API key you can bury in client-side widget code; a "secret" shipped to the browser is a public value with extra steps. So the endpoint protects itself instead: Cloudflare Turnstile runs on every submission (the widget asks its host shell for a token over postMessage, and each token is single-use and origin-bound), rate limiting sits behind that, and the server validates submissions against the fields the widget's schema declares, with a hard size cap. Free-form JSON from an anonymous visitor never gets stored as-is.

If you are wiring up your own public form endpoint this is the checklist that matters: bot check, rate limit, schema validation, size cap. Any of the four missing, and the internet will find it.

Edge 2: you are now rendering strangers' text on your own homepage

A guestbook renders untrusted input, in an iframe, potentially forever. The classic mistake is building the wall with innerHTML because it is Tuesday and it works. Then someone signs your wall with a <script> tag and their note executes instead of displaying.

The rule is one sentence: untrusted text goes through textContent, never innerHTML. On this wall, a note containing <script>alert(1)</script> renders as exactly those characters, which honestly looks right at home on a graffiti wall. Street art, not code execution.

(Defense in depth still applies: the widget lives in a sandboxed iframe on a separate origin from the app, so even a mistake there does not run next to anyone's session. But the sandbox is the seatbelt, not the driving.)

Edge 3: the wall grows forever, your homepage should not

Fugte embeds auto-size: the hosted widget posts its rendered height to the parent page, keyed by widget id, and the embedding page applies it. Lovely for a pricing table. A disaster for a wall that gets taller with every note, because in six months the homepage would be 94% guestbook.

The fix is a viewport: cap the applied height and let the wall scroll inside its frame.

var MAX_HEIGHT = 640;
window.addEventListener('message', function (ev) {
  var d = ev.data;
  if (!d || d.source !== 'webdyi-widget' || d.type !== 'resize' || d.id !== id) return;
  var h = +d.height || 0;
  if (h > 0) frame.style.height = Math.min(h, MAX_HEIGHT) + 'px';
});
Enter fullscreen mode Exit fullscreen mode

Below the cap, the frame hugs the content, so a young wall has no dead space. Past the cap, the frame stops growing and the wall scrolls internally, with an "open the full wall" link for anyone who wants the whole thing. Bonus: a capped iframe is also a layout-shift fix, since an embed that resizes itself mid-scroll is a CLS penalty you inflicted on yourself.

What I expect to go wrong

Public wall, public internet: some of it will be junk. Turnstile and rate limiting keep the bots out, but nothing stops a human from contributing "hi" seventeen times, and moderation means the worst entries need deleting by hand. I have made peace with this. A too-clean guestbook reads as fake anyway; the failure mode I actually care about (the wall becoming an attack surface) is handled by the three edges above.

There is also the possibility the wall just stays sparse and slightly embarrassing for a while. That is fine too. It is still a live form on a homepage, doing the one thing screenshots cannot: working.

Come vandalize it

The wall is on the Fugte homepage, or you can open the full wall directly. Sign it, try to break it politely, and if you find an edge I missed, the contact form (also a Fugte widget, naturally) works.

Want a wall of your own? The guestbook is a starter widget now, so you can put one on your own site and let your users sign it.

And if what you actually need is the boring version of this, a lead form with the submit path, spam screening, and inbox already attached, that starter is here.

Top comments (0)