The dev stack for RollEncounter
I am a web engineer by trade. I’ve been working on websites (front and backend) for the better part of the last 15 years, and while I still feel like I’m not even close to being the best, I do enjoy the puzzle of putting together a system.
It’s one of the reasons I started working on this app, honestly. In a world so full of, “Why learn anything if the computer can just do it for you” I still find myself puzzling over architecture, best practices, performance, and user experience.
So when I started working on this little app to manage D&D sessions, I had to start from the basics, but with a bit more than just cracking open some HTML and vanilla JavaScript (which to be clear would probably have been just fine).
The stack for RollEncounter
Here’s what I’m using to write RollEncounter:
React
My background and training lies in React, so I kept it simple. I am familiar with the framework, I know my way around the philosophy of how to build an app using React, so there’s no real reason for me to change that.
There’s a lot of state management happening within the app: session content, NPC names, PC health, etc., etc. At the beginning I was just using useState and useContext for isolated state management, but as it grew over time I was a bit leery of scalability.
My initial thought was to use something like Zustand or Redux for improved DX, but at the end of the day there’s not that much that a state management library would be giving me that couldn’t already be accomplished with React out of the box.
Fortunately the state is fairly pluggable as written so if I ever want to swap out for something else, it’s not too big of a lift.
The app state is written with the same concept of a store, so that subscribers to the individual aspects of state only force re-renders of what is subscribed to it, not the entire app. I have a custom store with useSyncExternalStore() which uses similar structure to Redux with the concept of state slices.
For example, if you update a player’s health, it should only impact where that player’s health is rendered (i.e. where a party is displayed), not the entire app. +1 for performance.
Vite
I’ll be honest, I haven’t used Vite more than a handful of times, but as a bundler it’s hard to beat. It’s fast, it has pretty much zero boilerplate, and gives me what I need without a ton of extra bullshit.
I’ve used Next.js more, but 1) I’m not a big fan of Vercel, and 2) Next.js tries to be everything for everyone, and I don’t need 90% of what it offers that I can’t just write myself with Vite and React.
Cloudflare Workers for hosting
Both the app and the public site are hosted through Cloudflare Workers. I find Cloudflare to be incredibly simple and the security and DX it offers are unparalleled.
Even though I’m working out of the same repo for both sides, there are two different workers that handle the app and site to build and publish.
With Vite it’s something like a 40-second build-and-publish timeline. Pretty incredible.
Because RollEncounter supports image uploads for maps or references, I needed something to handle media well enough. An R2 bucket is excellent for this purpose, and the server handles all the size limits and file checking. It also handles a bit of image optimization so nobody’s serving 15MB PNGs.
Supabase
This was a pretty easy decision overall. I could set up something like a D1 connection in Cloudflare for data management but Supabase has really straightforward Postgres DX as well as authentication management.
Not too much to say about this one, but it felt like the right fit, and fortunately one of the best bits about Postgres is that I can migrate to something else if I need to fairly easily, except for the authentication piece.
CSS modules and plain ol’ HTML
One of the big annoyances I have with modern web development is the idea that we need every imaginable popular library and that’s the only way to make it happen.
Look, I’m a fan of TailwindCSS overall, but for a project like this it’s close to overkill. I considered other component libraries like ShadCN and Radix but oddly enough it felt it would slow me down more than the inverse.
CSS and proper HTML tags handle most of the problems that exist in an app like this. I don’t need 5,000 components for <details>, <abbr> and <summary>.
Another great reason to use Vite: CSS modules. Specifically-focused chunks of CSS that don’t require a global.css file? Yes please. I use a myriad of CSS variables for various reusable tokens so I’m not repeating myself unnecessarily. This models itself after TailwindCSS and a couple other popular concepts.
I might regret it later, who knows.
Astro
I needed at least one landing page for people wanting to learn more about the app before signing up. My options were “just write a simple HTML page”, “use a big library you don’t need”, or “generate it at build time”.
The main thing I really wanted was real components I use in the app to be demoable on the homepage. It’s one thing to show a screenshot or a video, and another thing completely to put a part of the app into the hands of a potential user.
A blog would be good, too (hello).
I’m no Astro expert either, but it’s fast. It took almost no time to set up, and importing components from the app is a breeze. As long as it has its own demo state that can be fiddled with, it’s a great way to actually showcase what’s happening when you sign up.
A handful of other little bits
- The blog uses
.mdxso I can write these blog posts in Markdown and React and display them properly with Astro. pnpmfor package management, Vitest for any unit testing, and ESLint for linting. Fairly standard.
I hope this has been interesting and insightful! I may go more into the actual code related to the app at some point, but this post is just for shining a bit of light behind the curtain.