avlogo
Spider
Home
About
WorkSpace
Contact
Book a Call
avlogo

Aviralyadav Aviralyadav

I'm Aviral - a full-stack developer, freelancer & problem solver. Thanks for checking out my site!

Genral

  • Home
  • About Me
  • Projects

Specifics

  • Guest Book
  • Bucket List
  • Uses

Genral

  • Home
  • About Us
  • Projects

Specifics

  • Guest Book
  • Bucket List
  • Uses

Contact

  • Contact Us
  • Links
  • RSS

© 2025 Aviral Yadav. All rights reserved.

Privacy Policy•Terms of Use
Made withCodeForTravel
All Projects

Curated Work / nextype

NexTypeNexType

A typing-speed test with real mechanical sound and live races.

NEXT.JSREACT.JSTYPESCRIPTTAILWIND CSSMOTION.DEVAUTH.JSVERCEL
Visit Live Site
Type
Full-Stack Web App
Role
Full-Stack Developer & Designer
Timeline
2025
Status
Live
NexType screenshot 1
NexType screenshot 2

Overview

NexType is a browser typing-speed test built as a real product rather than a toy: you type words, quotes, or code, and it measures your WPM and accuracy live, plays a genuine mechanical keyboard sound on every keystroke, and lets you race friends in real time. The core test is completely free and needs no login; signing in with Google or GitHub layers on cloud-synced settings, a dashboard with streaks and a weak-key heatmap, a global leaderboard, and live multiplayer races. It runs on Next.js 16 (App Router) with React 19 and strict TypeScript, a Turso (libSQL) database through Drizzle ORM, Auth.js v5 for identity, Pusher Channels for real-time races, and Serwist for offline PWA support. The engineering focus was a typing engine that stays perfectly accurate under fast input, and multiplayer that stays consistent on a serverless platform that cannot hold a socket open. Source is on GitHub at github.com/aviralyadav-av/NexType.

A browser typing-speed test that measures WPM and accuracy in real time, plays a genuine mechanical keyboard sound, and lets you race friends live — with cloud-synced settings, streaks, a weak-key heatmap and a global leaderboard once you sign in.

Key Features

  • Real-time WPM, raw WPM, accuracy and consistency, measured on every keystroke.
  • Modes for timed (15/30/60/120s), words, quotes, code (JS/TS/Python) and endless zen.
  • A real mechanical keyboard sound engine and six accent themes with no flash of the wrong color on load.
  • Live 1v1 races over Pusher with a shared word list, countdown, and server-authoritative finish order.
  • Cloud sync, daily streaks, a weak-key heatmap and a global leaderboard after Google/GitHub sign-in.
  • Installable PWA with offline support via Serwist.

Why I Built This

I wanted a typing test that felt like a real mechanical keyboard, not a web toy — instant, tactile, and honest about your numbers. Most browser typing tests either lag under fast input or fudge the WPM math, so I set out to build an engine that stays byte-accurate even when keystrokes arrive faster than React can re-render, and that reports the same standardized WPM the rest of the typing world uses.

It also had to be more than a solo tool. The parts I cared about most — a live head-to-head race, a streak that survives across devices, a heatmap that shows which keys actually slow you down — all require a real backend. So NexType is deliberately login-gated: the full test is free and anonymous, and signing in unlocks the cloud-synced, multiplayer half without ever getting in the way of just sitting down and typing.

The Typing Engine: refs as source of truth

The whole test lives in one custom React hook, use-typing-test. The core problem is that React state updates are asynchronous, so reading state inside a keydown handler that fires dozens of times a second gives you stale values and miscounts. The fix is a strict discipline: every value the keystroke handler and the one-second timer need — the current word index, what has been typed, the start time — is kept in a ref (a synchronous, always-current box), and state only mirrors those refs for rendering. Refs are the source of truth; state is just the UI's view of them.

A single global keydown listener routes each key: a printable character types, space advances the word, backspace corrects, and the first real keystroke starts the clock and the interval. WPM is the standard (correct characters / 5) / minutes, raw WPM counts everything typed, accuracy is correct over total keystrokes, and consistency is derived from the variance of your per-second WPM — so the results screen describes not just how fast you typed but how steadily.

hooks/
  use-typing-test.ts   # the engine: refs + timer + WPM math
  use-settings.tsx     # settings context (localStorage + cloud)
  use-sound.ts         # mechanical keyboard sound engine
lib/
  wpm.ts               # WPM / raw / accuracy / consistency
  anti-cheat.ts        # >80 WPM statistical guards
  words.ts  quotes.ts  code-snippets.ts
db/
  schema.ts            # user, scores, key_stats, daily_activity, races
components/
  typing-test.tsx      # global keydown -> engine
  Race/  Dashboard/  Keyboard/
  • Refs over state Every hot-path value (wordIndexRef, typedRef, startRef) is a ref; state only mirrors it for rendering, which keeps counting exact under input faster than React can re-render.
  • Standardized WPM A word is fixed at five characters — the universal typing-test convention — so scores are comparable with every other tool instead of being subtly inflated.
  • Modes as data Timed, words, quote, code and zen are one TestType union; each just defines how words are built and how the test ends (the clock, the last word, or Shift+Enter for zen).
  • Anti-cheat that never flags real typists Statistical checks for bot-like flatness, inhuman consistency and impossible bursts only run above 80 WPM, so normal runs are never touched; flagged runs simply do not count on the leaderboard.

Real-Time Races over Pusher

NexType deploys on Vercel, which cannot run a persistent WebSocket server, so live races run on Pusher Channels instead of a socket I would have to host. Creating a race stores a shared word list and config server-side; both players join a presence-race-<id> channel, a 3-2-1 countdown runs locally on each client, and typing progress is relayed as progress events through a thin API route. The finish order is never trusted to the client — the server stamps each player's place using an authoritative finishedCount, so two people finishing at almost the same instant still get a correct, consistent result on both screens.

  • Managed pub/sub over self-hosted sockets Pusher gives reliable presence and messaging without running realtime infrastructure on a platform that kills long-lived connections.
  • Server-authoritative finish Placement comes from a server-side counter, not client timing, so the race result cannot disagree between players or be gamed.
  • Shared, seeded word list The race's words are generated once and stored, so every racer types exactly the same test.

Data: Drizzle ORM on Turso

Persistence is a Turso (libSQL) database accessed through Drizzle ORM, so the schema is written in TypeScript and the queries are fully typed end to end. The tables are small and purpose-built: user for identity, scores for every saved run (which powers the leaderboard and carries the anti-cheat suspicious flag), key_stats for per-user per-key correct/incorrect counts (the weak-key heatmap), daily_activity for per-day test counts (streaks and the activity calendar), and races for a race room's shared config. Applying a schema change is a single npm run db:push away.

  • TypeScript schema db/schema.ts is the one source of truth for tables; Drizzle generates typed queries, so a column rename surfaces as a compile error rather than a runtime one.
  • Edge-friendly libSQL Turso's libSQL is fast and lightweight to reach from serverless functions, which suits a read-heavy leaderboard and per-user stats.
  • Stats as first-class tables key_stats and daily_activity are modeled explicitly, so the heatmap and streaks are simple aggregate reads instead of being derived on every request.

Auth, set-once usernames & settings

Identity is Auth.js v5 with Google and GitHub OAuth, using database sessions through the Drizzle adapter rather than JWTs, so a stable user id backs every score and race. On a first sign-in the user has no username, which triggers a blocking gate: availability is checked live as they type, the name is validated against a strict lowercase-alphanumeric rule (3–20 chars), and once chosen it can never be changed — leaderboards and race history stay stable. Settings live in localStorage for anonymous users and sync to the cloud once signed in, so themes, sound and mode preferences follow you across devices.

  • Database sessions A session row plus cookie (not a JWT) via the Drizzle adapter gives every run and race a stable, real user id.
  • Set-once usernames A live availability check, strict validation, and immutability after selection keep the leaderboard's identities permanent and clean.
  • Local-first, cloud-synced settings Preferences work with no account and upgrade to cloud sync on login, so the free test never depends on a server round-trip.

Performance, Themes & PWA

Because the test is the product, first paint and input latency matter more than anything. A tiny inline script in the root layout runs before the page paints, reads the saved accent and font from localStorage and applies them to the html element, which eliminates the flash of the wrong theme on load. Six accent themes are pure CSS via data-accent attributes and Tailwind v4 design tokens, so switching is a single attribute change with no re-render. Serwist generates a service worker for offline/PWA support, and the site is tuned for search and answer engines with a central SEO config plus JSON-LD structured data for Organization, WebSite and an FAQ.

  • No-flash theme A pre-paint inline script sets the accent/font from localStorage before first paint, so themed users never see a wrong-color flash.
  • Token-driven themes Accent themes are [data-accent] plus @theme inline tokens — instant, render-free theme switches.
  • Installable & offline Serwist ships a service worker and a web manifest, so NexType installs as a PWA and keeps working offline.

Key Decisions

  • Refs as source of truth over pure state — Keystrokes outrun React renders, so mirroring hot-path values in refs and treating state as a view was the only way to keep counting exact at high WPM.
  • Drizzle + Turso over a heavier database — A typed TypeScript schema on libSQL kept the data layer light and edge-friendly for a read-heavy leaderboard, without an ORM or database that would be overkill for the domain.
  • Pusher over self-hosted WebSockets — Vercel cannot hold a socket open, so leaning on managed pub/sub gave reliable races without running my own realtime server.
  • Server-authoritative race results — Trusting client timing for placement invites both bugs and cheating; a server-side finish counter makes the result consistent and fair.
  • Set-once usernames over editable ones — Immutable handles keep the leaderboard and race history stable and prevent impersonation churn, at the cost of a single one-time choice.
  • Login-gated extras over a login wall — The full test stays free and instant; only the social and cloud half needs an account, so the core experience never waits on a server.

What I Learned

  • Async state is the enemy of accurate input — The biggest correctness win came from realizing state cannot be trusted inside fast handlers, and rebuilding the whole engine around refs instead.
  • A single design token can silently break fonts — Tailwind's default --font-sans quietly overrode the next/font variable until I re-declared it inside @theme inline; small tokens have outsized reach.
  • Mobile needs a real focus target — The soft keyboard never opened until I added a hidden focusable input — a reminder that 'works on desktop' says nothing about touch.
  • Serverless reshapes realtime — You do not fight the platform's lack of sockets; you design around it with managed pub/sub and server-authoritative state, and the result is simpler to own.

Tech Stack

NEXT.JSREACT.JSTYPESCRIPTTAILWIND CSSMOTION.DEVAUTH.JSVERCEL

Like what you see?

Explore more work or get in touch.

More ProjectsGet in Touch