tldr: I’ve built a multiplayer Sudoku game which you can play at Countdoku.app. Read on if you are interested in the technical details, (they’re cool, honest!)

Many years ago I became interested in how to generate Sudoku puzzles efficiently, as a thought exercise. Having solved this fairly well writing a program in Java, I continued to build a small multiplayer Java game where people could play the same Sudoku puzzle, taking turns to see who would win. This was long before social media or even Web2, and it ended there.
A few weeks ago I picked up the idea and decided to port it to modern technologies, namely the modern (as of 2022) Web. I decided to build a Progressive Web App (PWA) with NextJS, React, running on Vercel.
Priorities
My priorities with the project were
- Keep it simple. It doesn’t need to be fancy, just a game you can pop into when you feel like some diversion.
- Make it fast. It should work well on slow networks and cheap phones.
- Easy to share. Players should be able to get others to play with them with minimal effort.
- Kid friendly. I have two young daughters and wanted to make something they can enjoy playing (they love it!)
Technical choices
I am a huge fan of NextJS and React. They make building a web application far easier than it has ever been in the past (I’ve been doing this since 2003 or so, trust me, I’ve seen things you people wouldn’t believe…..). The combination of React’s relatively simple API & easy componentisation, NextJS’ developer environment, and Typescript means I can knock out code quickly and reliably.
Deploying NextJS apps works best on its parent company Vercel’s platform. Of course you can deploy NextJS apps anywhere, but Vercel really is the perfect match for it. Just push to a Github repo and very soon it’ll be live on the web. I also have other Vercel deployed projects, so adding one more was a no-brainer.
My initial plan was to use a persistent connection to receive live updates when another player made a move, so I decided to use Firebase & Firestore, which have SDKs that support this. I’ve had a terrible time in the past using Firestore for complex applications, as it has so many gotchas, however this project was going to be simple, and they even seem to have fixed some gotchas in the past few years – you can now run a query with a ‘not equals’ condition. So fancy!
Need for speed
My initial version of the web app used the standard NextJS and React deployment, with JS running both on the server and on the client. However I found that this was very slow to download on a phone, with over 250kb of JavaScript to download, parse and execute before any application code was used. This was split between React and the large bundle of JS that NextJS sends down the wire to enable fast page transitions, as well as the FirestoreLite SDK.
I realised that I didn’t need any of that. After rendering the page on the server side, the only visual changes that happened during play were:
- Changing CSS classes on the Sudoku board to highlight some cells.
- Showing and hiding dialogs.
Knowing this, I disabled all client side NextJS and React JavaScript by exporting the following from the page JS file.
export const config = {
unstable_runtimeJS: false,
};
I then wrote a single Javascript file called browser/index.js
in which I put most of the application code. I separated some other code out into their own small files, e.g. everything to do with accessing the API, and wrote a simple script for copying/transpiling any utility code I shared with the server side, which you can find on Github.
Then, it was a fairly simple thing to use Browserify and Tinify to build the resulting JS file and put it in the public/js
folder, to be loaded. While developing, I used node-watch
to listen for code changes and rebuild the main.js
file.
All of this was duplicating what NextJS gives you for free, but the result was that I loaded 19KB of JavaScript instead of hundreds of KB, and every byte was application code that provided functionality to the user.
Goodbye Client side Firebase
As for the Firebase/Firestore code, I removed the client side SDK, and moved all that functionality to a few simple APIs, which used the Firebase Node SDK instead. Then I simply called these using fetch()
on the client. The downside of this is that I no longer had access to the real time updates that Firebase can provide, but those are only available through its full SDK, not the Lite version, and that is simply huge. Replacing it with a regular polling of an API endpoint gives the user basically the same functionality, and I store timestamps on every user change, so the API only returns the changes since your last request, keeping the payload small.
Finally, I added a simple service worker to make this a Progressive Web App. I basically copy/pasted the great example from the Chrome Dev team, and modified it slightly to suit my needs. This not only makes some requests on repeat visits quicker, it also allows the app to run in full screen mode, hiding the browser chrome, which suits this game style of app.
Result
The final result is that Lighthouse on Google Chrome gives a 100 performance score. This is largely because, once the HTML is sent down by NextJS on the server, only 16KB of JavaScript is loaded, and none of it renders anything on the screen at page load time, so there are no layout shifts.

Conclusion
This method of building apps will not suit every use case of course. There are applications where the UI needs to change significantly as the user interacts with it, or new data needs to be loaded and displayed with a UI or template that has not yet been rendered.
However, conversely there are many applications where this is a perfectly valid method of building fast, responsive user experiences. I’m currently building a far more complex application in a similar fashion (hopefully launching in the coming months), and this is working very well. When large UI changes are required, link to a new page and load it. Otherwise have small scripts that slightly modify the DOM as needed, as well as some reusable code for showing/hiding areas of the screen (dialogs, drawers etc), and loading new content rendered server side when necessary.
NextJS and React really do provide a fantastic developer experience, but for anything I want to run on mobile, they’re just too heavy to run client side. This approach keeps the great developer experience, keeps most of the code running on the server, and also gives a great experience to your users no matter how cheap their phone or shoddy their network connection is. Everyone wins.
Finally, I’d love any feedback you have on Countdoku as a game! Feel free to tweet at me, or email me. Try out the easiest mode with young kids, I’ve found they get a kick out of puzzles they can actually solve, before they graduate onto the much harder puzzles later.
Epilogue
One alternative I considered was to use Web Components on the client side. They’re definitely interesting, but since I had a good server side rendering story already, and didn’t need to technically render any new UI on the client side, they would have been overkill. Plain old JS was more than sufficient to make calls to the API, show/hide some pre-rendered dialogs and fiddle with CSS class names on existing DOM nodes. I might give them a go for a future side project however.