RUEN
Start a project

Studio product

Quizar

A live quiz platform: the host opens a room, questions go on the shared screen, players answer from their phones with a code.

players per room
500
question types
7
test suites
35

Open the site — quizar.cc ↗

Quizar first screen

The host builds a quiz and opens a room, questions go on the shared screen, players join from their phones with a six-character code or a QR. Any published quiz can also be played alone — no host, no account.

Where the difficulty actually is

Building a quiz app is not hard. Three things are, and all of them start once thirty people with different connections are in the room.

Scores must not depend on connection speed. That is the standard complaint about platforms like this: the winner is whoever has the better Wi-Fi, not whoever knows the answer.

A dropped connection must not cost the game. Phones lock, tabs reload, school Wi-Fi blinks — and none of that should cost a player their name, their score or an answer they already sent.

The correct answer must not physically sit on a player’s device while the question is open. Otherwise one open inspector ends the game.

The server is the only source of truth

The client never decides when a question ends, which answer is right, or how many points to award. It renders what the server sent and reports what the player did.

Game rules live in pure modules with no I/O: the room state machine is a reducer returning declarative effects, alongside the scoring formula, per-type grading and latency compensation. Everything that depends on time, the network or the database sits behind interfaces — room store, broadcaster, clock, persistence. The engine is therefore fully testable without a socket and without a database, which is exactly why it carries 35 test suites rather than two smoke runs.

Scores that ignore the network

points = base × correctness × (0.70 + 0.30 × speed) × streak multiplier

An instant correct answer scores 100%, a correct answer on the buzzer scores 70%. The maximum advantage speed can buy is about 43%, not double — a slow correct answer always beats a fast wrong one.

Latency compensation. The server only sees “question sent” and “answer received”, and two network hops sit in between. So the client measures thinking time from the moment the question paints. The server does not trust that number: it clamps it into a window whose width is the RTT the server itself measured for that socket. You can cheat by exactly your own latency — the amount that was being taken from you unfairly anyway.

The first 15% of the time, capped at 1.5 seconds, carries the full speed bonus: that is reading time. Time spent paused is subtracted from thinking time, otherwise discussing a question mid-game would zero the bonus for everyone at once.

Players see the breakdown: how much came from correctness, from speed, from the streak.

Reconnecting

On joining, a player gets two tokens. A short-lived signed JWT authorises the socket handshake; a long-lived return token is kept by the browser. The server stores only the SHA-256 of that token, so a database leak does not hand anyone someone else’s session.

Coming back restores name, score, streak, current state and any answer already submitted. Host screens restore the same way: a control panel reloaded mid-question immediately gets the current question, the answer count and the review, rather than an empty stage until the next one. The host counts as gone only when the last of their screens closes — a closed projector tab while the control panel is open tells players nothing.

Correct answers never reach the phones

The client payload is assembled from an allowlist of fields, not by stripping a complete one. The difference matters: with an allowlist a new field on the question model cannot leak by default — it has to be added deliberately.

Tests cover it: they serialise an open question of each of the seven types and assert that no marker of correctness, no explanation and no accepted answers are present.

A frozen snapshot of the quiz

When a game starts the quiz is frozen. Answers reference question identifiers from the snapshot as plain strings rather than foreign keys, so editing or archiving the quiz later cannot retroactively change the results of games already played.

Seven question types with partial credit

Single answer, multiple answers, true/false, short answer, ordering, pair matching and a points-free poll. Partial credit where it means something: hits minus false positives for multiple choice, a normalised Kendall coefficient for ordering, the share of correct pairs for matching.

Short answers are normalised: case, extra whitespace and punctuation do not affect the result, and typos within tolerance still count as correct.

Images are re-encoded, not validated

An uploaded file is decoded and encoded again as WebP. This is not belt-and-braces, it is the check itself: a file that is simultaneously a valid PNG and a valid HTML document passes any signature check but does not survive re-encoding. EXIF metadata goes with it — including where the photo was taken.

The format is decided by the decoder, not by what the browser claimed. SVG is rejected even though it decodes perfectly: it is a document that can execute scripts, and serving it from your own domain is not acceptable.

One port, one process

The App Router cannot hold a WebSocket server inside a route handler, so a custom entry point starts Next programmatically and attaches Socket.IO to the same HTTP server. One port, one process, one command to run it.

By default room state lives in process memory. Set a Redis URL and three things switch to it at once — room storage, rate limiting and socket broadcasting — and nothing else in the code changes.

Accessibility

Answer options differ by colour, shape, number and key: the game works in greyscale and on a washed-out projector. There is a colour-vision mode using the Okabe–Ito palette, reduced motion from both the system setting and the profile, full keyboard control, and aria-live on everything that updates by itself. The layout holds from 360 px up to a projector.

TypeScript · Next.js 16 · React 19 · Socket.IO · PostgreSQL 18 · Prisma 7 · Auth.js · Tailwind CSS · Vitest · Playwright