For the past few months, I've been working on Coachtracker in my spare time. It started off as a vague request from my mother to build a tool to help her manage her professional coaching business—and has escalated into a micro SaaS and the single biggest software project that I've built from greenfield.
This has been a really fun experience, but I think I've been missing out on some online Klout by not writing about the process. I want to rectify that. So here, I will present a general overview of how we built Coachtracker.
This will be an ongoing series. If there's anything in particular you'd like me to cover, please get in touch.
When I started building Coachtracker, I had just been learning about Remix. We used it to build Contentful's internal meme platform, Memetentful, which was a great experience. So I decided to continue with it.
So far, I don't regret the decision at all. Some things I like about Remix are:
It encourages you to minimize your client-side code. A lot of frontend codebases end up being extremely confusing rat's mazes, mostly because of the requirement to keep your data in sync between the backend, the frontend, and the DOM. If you start with a clear idea of how to do that, you will at some point get confused and upset. Remix helps with this by strongly encouraging you to split up your app into routes that are responsible for loading their own data. In most cases, when a user is able to edit data in Coachtracker, I am able to rely on the platform. There are a lot of forms, very few controlled components, and not too many hooks. When I do want really custom behavior, the blast radius of any complex UI code is limited to that page.
An example of some slightly complex UI
It's very flexible. You can really do what you want. Any route of your Remix app can be a fully-fledged SPA. You can write your own server, you can change how routing is created. It gives you some core tools and lets you go wild.
It enables monoliths, which are nice. Remix encourages you to build a monolith. And for a small project like this, a monolith is clearly the correct approach. I love that I don't have to run two projects, that I don't need to deploy a frontend and a backend, and that I don't need to worry about writing the code to load the data for most of my routes. Everything has been easy; it's what I imagine writing Ruby or PHP feels like—but I get to use TypeScript.
I'm using Prisma, mostly because the Remix template I used suggested it. I'm not sure how I feel about this! It's quite nice, easy to use, and fast enough for what I'm doing at the moment. However, I'm concerned by a few things:
The killer feature of Prisma has been making migrations easy. But there are other tools out there that can do the same. I think it's the part of my tech stack that I am most likely to remove.
This was the first time I've used Tailwind in anger. It's really good! There are only two ways I want to write CSS from now on, and those are CSS modules or using Tailwind.
I'm using Tailscale to enable a private connection between fly.io, where I'm hosting the app (love it), and AWS, where my database lives (ehhh).
I originally considered setting up WireGuard myself, but I'm very glad that I didn't. Tailscale is nice and easy.
Some stuff I've added to Coachtracker very recently:
My 404 Page was a boring and demonic blank white page. I adapted a 'make a 3d cube' codepen into this new nightmare.
This was a pretty essential feature for an app that is focused on scheduling. I think the messaging about this needs to be improved, and I want to surface what timezone dates are being shown in throughout the app. But it's a good first step.
The booking flow was confusing on small screens, since you would select a time slot and then a confirmation button would appear—often off the screen. This confirmation is now shown in a modal. Quick, easy win, but this flow will get more TLC in the future.
I will be writing more about how I've built Coachtracker in the near future. Topics I want to cover include:
Thanks for reading! If you'd like to get in touch please contact me