01 / From watching to building
I followed these spaces without really participating.
Whenever I get into something, I tend to consume a lot of content around it. Gaming, startups, and building were no different. I followed podcasts, launch posts, architecture writeups, founder stories, and people sharing their work in public. It made me feel connected to those communities, even though I was mostly a spectator. I do not think that time was wasted, but watching other people make things was not a substitute for making something myself. That shift from watching to doing is also why the title is a nod to Aaron Francis, who has talked a lot about the same mindset: you can just do things.
I already had several ideas from playing Riftbound on TCG Arena, a browser-based platform for playing different trading card games online. On February 8, while I was still considering the project, I asked the Riftbound community what they would want from a dedicated simulator. Two days later, I made the first commit. I am publishing this exactly six months later.
Years in technical consulting and software development, along with founding a startup, had given me a reasonable sense of how much work even the first version would normally take. AI coding tools changed what felt realistic. I had already used them a lot, but Codex felt like a real step forward because it could work more independently than the tools I had tried before. That made the process much smoother and gave me the confidence to take on the simulator myself.
What I had thought much less about was everything that would come after the first version, from marketing and operations to scaling and the total time and money involved. At first, I was simply focused on making a product I wanted to use. If I had tried to account for everything upfront, I might not have started.
Technical noteStarting with what I knewNext.js on Vercel for the web app, with Convex for the game engine and database.
I did not spend long comparing stacks before I started. For the web app, I chose Next.js on Vercel because I knew both well, enjoyed using them, and could focus on the product immediately.
I knew React was not an ideal fit for a highly interactive simulator, but I did not expect a card game to put enough pressure on the browser and network for that tradeoff to matter much. As RiftAtlas grew, managing component rerenders across the board became especially painful. React Scan helped by highlighting which components were rerendering too often, so I could focus the optimization work where it mattered. The stack still worked, but supporting its eventual scale took much more optimization than I expected.
I chose Convex for the game engine and database. I had experimented with it a little before, and it matched what I wanted from the stack: the database, backend functions, and realtime updates in one system, with end-to-end type safety between them. The database schema, server logic, and frontend calls shared the same TypeScript contracts, while the backend I tested locally could be deployed without changing how it worked. That made the setup especially comfortable for moving quickly. What I severely underestimated was the demand RiftAtlas would eventually place on the platform.
02 / Building the first version
I knew the experience I wanted to build.
Playing on RiftAtlas should feel smooth, clear, and easy to follow. I focused on the small moments that can make online play awkward, such as correcting mistakes, handling routine actions, or showing the other player what you intend to do.
The first release focused almost entirely on that experience. It had no accounts, deck builder, or saved decks. Players could use it without registering and imported decks ad hoc when they wanted to play.
I made the initial technical decisions quickly, but spent a huge amount of those five weeks refining how the first version felt to use. The more agentic capabilities emerging in AI coding tools let me work through longer, multi-step tasks at a pace that surprised me. The models were much better at some parts of the work than others and still needed close direction and plenty of iteration, especially on the UI, because I did not want the first release to feel unfinished. That speed did not translate into less work. I used the extra capacity to make RiftAtlas more ambitious instead. The work was intense and a lot of fun.
About five weeks after starting, I released the first version and announced it on Reddit.
03 / Learning from users
People started showing up.
The Reddit announcement got traction quickly. It also sparked some debate about how fan-made simulators fit within Riot's rules. What surprised me was that the attention continued after the post. People found the website, joined the Discord server, and shared RiftAtlas with others, even though I was not actively promoting it elsewhere. The site quickly went from a few hundred visitors a day to around a thousand. That already felt like a lot of people.
Around the same time, Khasino featured it in a video, and Silent Knight followed shortly after with a review and how-to. Seeing the word spread beyond my own announcement, and seeing people use and enjoy RiftAtlas, was a very cool feeling.
With more users came more suggestions and bug reports. I could respond, fix things, and hear what people wanted next. I had never built a project with an active community around it before. I was used to gathering feedback and building for users in a B2B context, but usually from a much smaller group. Having a public community discuss RiftAtlas, report problems, and influence what I worked on next felt very different.
While the community response was immediate, the operational issues emerged more gradually. As traffic grew, scaling issues and operating costs became a regular part of the work.
Technical noteRealtime gameplay generated more activity than I expectedConvex synchronized live game state throughout every match, making cloud usage too expensive despite repeated optimization.
The website slowly accumulated some cost on Vercel, but there was no urgent need to optimize it. Convex was a different story.
Using Convex as the realtime gameplay engine meant much more than saving the final result of a match. The live game state and the smaller interactions around it had to be written and synchronized throughout play. I had assumed a card game simulator would be a fairly light workload, but each match produced far more database activity than I expected. Even after repeatedly reducing reads and writes, the cloud usage still cost too much to be sustainable.
The issue was cost rather than everyday performance. Hosted Convex generally performed well, and from US-East latency was good nearby and remained acceptable from the US West Coast through Europe.
04 / Keeping up with growth
As more people used RiftAtlas, the work expanded.
There were now more matches to support, more reports and suggestions to respond to, and rising infrastructure costs to manage. Improving the simulator was still important, but it was no longer the only part of the work.
As the Discord server grew, I started looking for moderators. Brandon was one of the people who stepped in, and over time he became much more involved in running RiftAtlas.
- ProductImprove the simulator
- New features and polish still mattered, but every bug now affected a growing number of users.
- OperationsKeep matches running
- Reliability and performance became part of the everyday work as match volume grew.
- CommunitySupport the community
- Feedback, bug reports, moderation, and deciding what to work on next all needed attention.
- SustainabilityMake growth sustainable
- Infrastructure costs were rising quickly enough that I needed a different setup.
Cost became the most urgent operational problem. When reducing reads and writes was not enough, I moved the backend from a managed service to infrastructure I operated myself. Costs dropped substantially at first, but reliability suffered as usage grew and infrastructure costs climbed again. It soon became clear that this would not be a long-term solution.
Technical noteSelf-hosting Convex on RailwayThe migration lowered costs at first, but made Postgres scaling and reliability part of the operating work.
Mostly good performance · usage costs too high
Lower initial cost · harder to keep reliable
Moving the same backend to Railway reduced costs considerably, but the tradeoff was taking responsibility for operating Convex and Postgres myself. I had to go much deeper into Postgres operations to understand how to scale the database for this workload. As match volume continued to grow, reliability declined.
The initial cost advantage narrowed too. Infrastructure spending rose again with continued growth. The migration addressed the immediate hosted Convex cost problem, but it did not provide a setup that could support RiftAtlas in the long term.
05 / Reworking RiftAtlas
RiftAtlas had grown far beyond its first version.
That migration bought time, but growth kept exposing its limits. Solving the long-term problem required more than another round of small improvements.
The biggest change was how live matches ran. The new architecture gives each match its own isolated room, allowing matches to scale independently instead of sharing one central game process.
This was a major rewrite and a fundamentally different model from the one I had started with. The transition caused quite a few problems at first, but it gave me a much better foundation. After more work and iteration, the new model became stable and reliable.
Development continued across the rest of RiftAtlas too. Almost every part of the original application was eventually replaced or heavily reworked. Some changes added pieces missing from the first release, including accounts, a deck builder, and saved decks, while others redesigned existing features or changed how things worked behind the scenes.
Keeping it affordable also required changes elsewhere, including how the website loaded content and delivered its assets.
The project was also becoming more connected to the wider Riftbound community. Brandon's role continued to grow, while creators and people running other projects in the space started reaching out about ways to work together.
Even with all that rework, I still think releasing the first version quickly was the right decision. Putting RiftAtlas in front of players as soon as it was useful showed that the project was worth continuing, while real use made it clearer what needed to change.
Technical noteHow the application architecture changedPartyKit took over live matches, while fewer Vercel requests and Cloudflare asset delivery addressed costs elsewhere.
In the original architecture, Convex served as both the game engine and database. In the new model, each live match belongs to a PartyKit room running on Cloudflare Workers and backed by a Durable Object. The realtime runtime now handles the whole live match lifecycle: creating and joining rooms, matchmaking, the lobby, setup, gameplay, chat, reconnects, spectators, and short-lived presence such as hover states and card pings.
Gameplay + product data together
Gameplay moved out · product data returned to hosted Convex
The room receives player actions and updates the authoritative match state, but it does not send every connection the same data. Each player and spectator receives a projection for their own view, so private hands and deck order stay behind the server boundary.
Recovery moved into the realtime runtime as well. Normal actions are stored as compact semantic patches rather than complete copies of the match. Full hidden state is saved at structural boundaries and periodically as patch history grows. If a room wakes or restarts, it loads the latest checkpoint and replays the ordered patches that follow it.
The normal gameplay path no longer waits for Convex or another external write. Once live matches no longer depended on Convex, I moved saved decks and the other persistent product data from the self-hosted Railway deployment back to hosted Convex.
Solving room lifecycle, persistence, and recovery took substantial additional work, but the result was a more robust and sustainable server-authoritative engine.
As RiftAtlas expanded beyond anonymous matches, I introduced user management with Clerk. I had already enjoyed using it on previous projects, so it was a natural choice when RiftAtlas added registration and sign-in.
The web app needed a separate set of changes. I removed prefetching in many places to reduce the number of requests reaching Vercel, and static assets moved to Cloudflare instead of continuing to be served through Vercel.
06 / Six months later
Where RiftAtlas is today.
Six months after I started building, RiftAtlas has recorded nearly 120,000 sign-ups, and players have saved close to half a million decks.

Technical noteWhat RiftAtlas looks like at this scaleThree applications, four shared packages, and roughly one billion database rows read in one recent week.
RiftAtlas grew into a monorepo managed with Turborepo. The active project now spans three applications and four shared packages, with nearly 180,000 lines of production code.
At the current scale, RiftAtlas's simulator Durable Objects recorded roughly one billion database-row reads and 126 million database-row writes in a recent week.

At this scale, even small bugs can become expensive. In July, I found that idle game rooms were not being cleaned up 🙈 Each cleanup alarm woke a room and moved its deletion deadline another minute into the future, so it kept its stored state and scheduled another alarm instead of deleting itself. The loop added alarm requests and storage operations for rooms nobody was using. Fixing the deadline calculation allowed those rooms to expire normally.

Convex subscriptions are another place where small decisions can create a lot of traffic. Each subscribed client keeps a query live and receives a new result when its dependencies change. I now treat subscription scope and update size as scaling decisions.
We are now preparing for our second large tournament. RiftAtlas has become a big part of online play in the Riftbound community, and people regularly tell me how much they enjoy using it. Hearing that still feels great.
Thank you to everyone who has played, shared RiftAtlas, reported bugs, suggested improvements, helped keep the community running, created content around it, or collaborated with us. I have really enjoyed seeing people use what I built and getting to work with others in the Riftbound space. The project would not be where it is without all of that support.
Writing this post made me step back from the next problem and look at the project as a whole. It is still kind of crazy to me what RiftAtlas has become in six months.
Most of it could not have been planned at the beginning because it only became visible once people started using RiftAtlas. I began by making a product I wanted to use and kept learning from what happened next. Six months later, I am glad I just did things instead of overthinking all the ways the project could go wrong.
