Dev.to WebDev πŸ›  Dev πŸ‘ 1 πŸ“– 5 min read

I built a "should I block you?" friend quiz on Cloudflare Workers β€” here's what I learned

A few weeks ago I kept seeing the same post on Threads and Instagram: "I'm blocking my fake friends 🚫 should I block you?" followed by a quiz link. Someone answers ten questions about themselves, shares the link, and fri

I built a "should I block you?" friend quiz on Cloudflare Workers β€” here's what I learned

A few weeks ago I kept seeing the same post on Threads and Instagram: "I'm blocking my fake friends 🚫 should I block you?" followed by a quiz link. Someone answers ten questions about themselves, shares the link, and friends try to guess the answers. Score low and you're "blocked".

It's a simple idea, and it spreads well because every player is one tap away from making their own quiz. I wanted to see how far I could take it as a side project, so I built my own version: Block You Test Me.

This post covers the parts that turned out to be more interesting than I expected.

Make a quiz, friends get instant feedback, then the verdict

The stack

  • Next.js 16 with OpenNext on Cloudflare Workers
  • D1 (SQLite) for quizzes and scores, KV for rate limiting
  • next-intl for 8 languages
  • No accounts, no login. The quiz maker gets a private scoreboard link instead.

Everything runs in one Worker. There's no separate API server and no image service.

1. Instant feedback without handing out the answers

Players see whether each answer is right as soon as they tap it. That makes the game much more fun, but it means the browser has to know the right answer before the quiz is submitted.

I didn't want the answers sitting in the JSON as plain values, so each question ships two choices plus a short hash for each one:

// server
h[k] = isCorrect(choice)
  ? sha256(`${quizId}:${questionId}:${choice.index}`).slice(0, 8)
  : sha256(`${quizId}:${questionId}:x:${k}`).slice(0, 8); // decoy

The client hashes quizId:questionId:index with Web Crypto and compares. Someone with DevTools open can still work it out, and that's fine for a party game. The score on the scoreboard is always calculated again on the server, so nobody can post a fake 10/10.

The wrong choice is picked from the creator's other options using the quiz ID as a seed. Every player gets the same pair of options, so scores are comparable.

2. Dynamic OG images (and when not to use them)

The share preview is what people see in WhatsApp before they tap, so each quiz gets its own image: "Will Mia block you?". It's rendered with next/og inside the Worker.

Two things I didn't expect:

  • Non-Latin names. The default font only covers Latin characters. An Arabic or emoji name made next/og fetch glyphs at runtime, which added 1–2 seconds, and Arabic came out reversed. Now names outside Latin and Latin-1 get a redirect to a static image. That covers most of my users, and the edge cases fail gracefully.
  • Bundle size. Adding OG generation took the Worker from about 1.2 MB to about 2.0 MB gzipped. That still fit, but it became the theme of the whole project. More on that below.

3. Story cards with a QR code

Most of the similar quiz sites I looked at had no Story image to save, and none of them had a QR code, so I added two:

  • an invite card for the quiz maker ("Will Mia block you?" + QR code)
  • a result card for players ("Mia will NEVER block me πŸ’– 9/10")

They're drawn on a 1080Γ—1920 <canvas> in the browser, with qrcode-generator (MIT, no dependencies) for the QR code. On mobile I try navigator.share({ files }) first. Instagram and WhatsApp's in-app browsers often can't download files, so the fallback is a full-screen preview with a "long-press to save" hint.

4. Sound effects without audio files

Every tap makes a small sound. There are no audio files: everything is synthesized with the Web Audio API, a few oscillators and gain envelopes. The whole sound module is about 120 lines.

The one rule that matters: only create the AudioContext inside a user gesture. My first version replayed the result fanfare when someone revisited a finished quiz, which counts as autoplay. Now results that are loaded from localStorage render quietly.

5. Eight languages, chosen by the data

I launched in English, then added Malay, Indonesian, Filipino, Spanish and Portuguese based on where the original trend came from.

Then I looked at Google Analytics. In the first three days the site had about 600 visitors, and more than half of them had Turkish browsers, mostly arriving from Google search. I hadn't planned for Turkish at all, so I added Turkish and Vietnamese the next day.

A few i18n details that helped:

  • English URLs have no prefix, and the other languages do (/tr/create). Existing links never changed.
  • Every page has hreflang links to every other language plus x-default.
  • No automatic redirects based on Accept-Language. Visitors get a small "View this page in TΓΌrkΓ§e" bar instead. Azerbaijani browsers are also offered Turkish, since the two languages are close.
  • Quiz IDs and answers don't depend on language. A quiz made in Turkish can be played in English with the same options and the same scoring.

Chat reveal before the score, and the Turkish version of the home page

6. Staying under the 3 MB Worker limit

The Workers free plan allows 3 MB (gzipped). Each language added about 70 KB, and the last feature, about 60 pop-culture questions split by region, pushed the bundle to 2.96 MB.

The fix turned out to be one line of code. I had merged two translation tables with object spread:

// before: the bundler can't drop the unused parts
QUESTIONS_I18N.tr = { ...trQuestions, ...trPopQuestions };

That made every translation reachable from the Worker entry, so nothing could be tree-shaken. Keeping the tables separate and picking one at lookup time saved 60 KB:

const table = id.startsWith("p") ? POP_I18N : QUESTIONS_I18N;

My takeaway: read the dry-run output before every deploy (wrangler deploy --dry-run prints the gzipped size).

7. Things I'd do the same way again

  • Make the share flow the main feature. Players finish 85% of the quizzes they start, and the "now it's your turn" button is where new quiz makers come from.
  • Keep data small. I only store a nickname, answers and scores. Quizzes with no plays for 90 days are deleted by a cron trigger.
  • Write the questions yourself. Original, gender-neutral questions with emoji options read better than copied ones, and you can translate them cleanly.

Try it

Make a quiz, send it to a friend, and see who gets blocked: blockyoutestme.app. There are also a Best Friend Test, a Couple Test and a Crush Test.

If you've built something viral on Workers, I'd like to hear how you handled bundle size and OG images. Questions are welcome in the comments.

I hope you like this tiny game.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.