<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Talha Shahid</title>
    <description>The latest articles on DEV Community by Talha Shahid (@talhashahid).</description>
    <link>https://dev.to/talhashahid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4163378%2F639a7291-1a60-4e75-b639-787d78708966.png</url>
      <title>DEV Community: Talha Shahid</title>
      <link>https://dev.to/talhashahid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/talhashahid"/>
    <language>en</language>
    <item>
      <title>I built a free, privacy-first set of classroom tools with a static Next.js site (no backend, no cookies)</title>
      <dc:creator>Talha Shahid</dc:creator>
      <pubDate>Mon, 05 Oct 2026 09:22:30 +0000</pubDate>
      <link>https://dev.to/talhashahid/i-built-a-free-privacy-first-set-of-classroom-tools-with-a-static-nextjs-site-no-backend-no-b00</link>
      <guid>https://dev.to/talhashahid/i-built-a-free-privacy-first-set-of-classroom-tools-with-a-static-nextjs-site-no-backend-no-b00</guid>
      <description>&lt;p&gt;Teachers use a surprising number of small web tools in a single lesson: a wheel to pick a student, a group maker, a seating chart, a timer, a noise meter. The frustrating part is that most of them make you retype your class list every time, and many show ads right on the projector in front of thirty students.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://classroomwheels.com" rel="noopener noreferrer"&gt;Classroom Wheels&lt;/a&gt;&lt;/strong&gt;: a free set of classroom tools that all share one class list. You save your class once, and the name wheel, random name picker, group and team generators, seating chart, timers, noise meter, bingo cards and scoreboard all use it. Here's how it works under the hood, and a few decisions that turned out to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. No backend at all
&lt;/h2&gt;

&lt;p&gt;The whole site is a static export of Next.js (&lt;code&gt;output: "export"&lt;/code&gt;), served as static assets from Cloudflare. There are no API routes, no database and no accounts. Every page is pre-rendered HTML, and the interactive parts are small client components.&lt;/p&gt;

&lt;p&gt;That decision did a lot of work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hosting is free&lt;/strong&gt; and fast everywhere, because it's just files on a CDN.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is simple to explain.&lt;/strong&gt; There's no server that could store student names, so schools don't need a long data-protection review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search engines and AI crawlers get full HTML&lt;/strong&gt;, because nothing important depends on client-side rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Class lists live in the browser
&lt;/h2&gt;

&lt;p&gt;Class lists are stored in &lt;code&gt;localStorage&lt;/code&gt; through a small Zustand store with &lt;code&gt;persist&lt;/code&gt;. Two lessons learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared school Chromebooks often wipe storage, so every class can be &lt;strong&gt;exported to a backup file&lt;/strong&gt; and imported again (JSON for everything, CSV for a single class).&lt;/li&gt;
&lt;li&gt;Storage can be unavailable (private windows, locked-down devices). Zustand quietly disables persistence if &lt;code&gt;localStorage&lt;/code&gt; throws, which broke hydration for me, so the store uses a storage wrapper that &lt;strong&gt;never throws&lt;/strong&gt; and the app keeps working in memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Absent students are marked once and every tool leaves them out.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fair randomness
&lt;/h2&gt;

&lt;p&gt;When a wheel picks a student, students notice if it feels rigged. Every pick, shuffle and spin uses &lt;code&gt;crypto.getRandomValues&lt;/code&gt; (Web Crypto) rather than &lt;code&gt;Math.random()&lt;/code&gt;. The wheel chooses the winner &lt;strong&gt;before&lt;/strong&gt; it starts spinning and the animation just lands on that slice, so every name has exactly the same chance. The name picker also has a "no repeats" mode: nobody is picked twice until everyone has had a turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. A noise meter that never records
&lt;/h2&gt;

&lt;p&gt;The noise meter uses the Web Audio API to turn the microphone signal into a single loudness number about 20 times a second, on the device, and then throws the audio away. Nothing is recorded or uploaded. It shows a traffic light or "voice levels" (0 silent to 4 presenting), and it can calibrate to the room's normal hum so it doesn't turn red when the class is quiet.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Present mode
&lt;/h2&gt;

&lt;p&gt;Every tool has a &lt;strong&gt;Present&lt;/strong&gt; button (or the &lt;code&gt;F&lt;/code&gt; key) that shows only the tool, full screen, in large type, with no menus or ads. Inside the Fullscreen API that meant a few edge cases: &lt;code&gt;Esc&lt;/code&gt; exits fullscreen in most browsers, so tools that use &lt;code&gt;Esc&lt;/code&gt; for their own pop-ups listen in the capture phase and call &lt;code&gt;preventDefault()&lt;/code&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Small, fast pages
&lt;/h2&gt;

&lt;p&gt;I set a budget of 180 KB of gzipped JavaScript per page and a script checks it on every build. The framework takes most of that, so each tool's own code has to stay small: no heavy UI or animation libraries. Lighthouse on mobile scores 86–98 for performance and 100 for accessibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Tests that match how teachers use it
&lt;/h2&gt;

&lt;p&gt;Playwright tests every tool at a Chromebook size (1366×768) and a phone size, including a check that the header and menus fit on a 320 px screen. A build-time SEO check fails if any page has a duplicate or over-long title, a wrong canonical URL, or FAQ markup that doesn't match the visible FAQ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's free and needs no signup: &lt;strong&gt;&lt;a href="https://classroomwheels.com" rel="noopener noreferrer"&gt;classroomwheels.com&lt;/a&gt;&lt;/strong&gt;. A few pages teachers seem to like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://classroomwheels.com/name-wheel" rel="noopener noreferrer"&gt;Name wheel&lt;/a&gt;: spin to pick a student, winners come off so everyone gets a turn&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://classroomwheels.com/random-group-generator" rel="noopener noreferrer"&gt;Random group generator&lt;/a&gt;: keep chosen students apart and mix ability levels&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://classroomwheels.com/wheels" rel="noopener noreferrer"&gt;20 ready-made wheels&lt;/a&gt;: brain breaks, sight words, times tables, would you rather and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you teach, or build tools for teachers, I'd love to hear what's missing.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>privacy</category>
      <category>education</category>
    </item>
  </channel>
</rss>
