Building cosn.io
I’m pretty happy with how this site turned out, considering I'm far from a frontend wizard, or even much of a developer these days. Most of my commits in recent years were during Brex's annual hackathon, Brexathon. My last production pull request was a simple change in April 2022, and the last time I actually shipped anything substantial was in October 2019, when we were all hustling to launch Brex Cash.
Yet, when I showed my friends what I had spent the better part of 2-3 weeks on, the most common reaction was shock that my last post was nearly a decade ago. It's a stark reminder of how much time has passed and how easy it is to let old passions and commitments fall by the wayside. Ironically, the first post was about my public commitment to write more. Whether it's code or words, looking back at your younger self's writing is always a bit cringeworthy - a feeling that only intensifies with the passage of time. Must be tough for authors – I don't know how they manage it. I should try to meet more of them, especially now that I'm in L.A.
But I digress. Other than sharing my observations and experience with anyone curious, hopefully I'm able to nudge some other rusty engineering leaders to get back to their coding roots after seeing both the struggles and the simplicity from my experience.
Motivations
As I've been transitioning away from my day-to-day duties at Brex over the past few months, I wanted to fill that time with things I've neglected, hoping to rebuild some habits before diving into my next adventure. Coding was high on that list, and the more I did it, the more I realized how much I'd missed it. Of course, it's almost always more fun to write code for hobby projects than to work in a massive, technically-indebted codebase with complex CI/CD pipelines and the looming threat of introducing bugs you can't personally commit to fixing.
The next question was what to build. Most of my professional engineering career has been focused on backend services and systems. The last time I wrote any significant frontend code was when I built the original Windows Azure sign-up flow [0]. That project made me realize I'm much more passionate about backend systems, and for the rest of my time at Microsoft and Stripe, my frontend contributions were minor.
One reflection from my time at Brex was that I've not managed to bridge the cultural divide between frontend and backend [1], and part of me thinks we should've switched to a full-stack model when we decided to evolve from Elixir [2]. This seemed like the perfect opportunity to scratch that itch and build some intuition around frontend development. Karri's tweet inspired me to build a personal website as a playground [3].
Initial iteration
I'm a strong believer in using the right tools for the job. After several mentions of Eleventy and reading through their comprehensive documentation, I started building. It went quite well at first – I put together a few layouts using Nunjucks and Markdown templates, and had a basic skeleton up and running within two days. But by day three, I decided to abandon that path and look at Next and Remix instead. There were two reasons for this:
- The more I built, the more specifically complex things became, particularly with Nunjucks and Eleventy modules.
- While that was all well and good, a big part of building this playground was to try out technologies I'd use beyond this specific (and arguably simplistic) use case.
I couldn't see myself using Eleventy for anything other than a static website, which is what the framework is designed for. I wasn't getting the learning satisfaction I was craving, so I tacked.
Next vs Remix
To embrace the full-stack mantra, you really only have one language choice: JavaScript. And when working in a codebase at scale, I've found that a robust type system is necessary, so JavaScript -> TypeScript. It doesn't take much research to see that React has won the battle of frontend frameworks, and within that, the top React frameworks are Next and Remix. The holy war characteristics are reminiscent of Vi vs Emacs, Debian vs Ubuntu, MySQL vs PostgreSQL, Python vs Ruby, and so on. So I'll just point out a couple of personal observations and my rationale for the choice:
- Both frameworks have a large overlap in terms of characteristics and abstractions.
- As such, both frameworks require some experience to avoid common pitfalls.
- Next uses more magic than Remix, which means more pitfalls (e.g. dynamic rendering, caching, server actions).
- At the same time, Next has a larger community, resulting in more examples and components specifically for Next compared to Remix.
Ultimately, I could see myself using either of these in a professional environment. I ended up going with Next this time because I already have enough things to (re)learn, so I chose the framework with the broadest content available.
Learning curve
After going through a few YouTube channels (H/T Wesley and Guillaume) and GitHub repos (H/T Lee), I started rebuilding the existing skeleton and looked at some Tailwind components to avoid needing a design degree in the process.
The first theme of complaints was around CSS. It honestly hasn't changed much from what I remember, meaning I still find it intimidating and frustrating. Personally, I found Tailwind CSS easier to manage, but I can definitely see the arguments against the utility-first approach. And most designers rely on Figma to handle the majority of their project's CSS anyway.
Next came what I'd describe as the "random bugs stage":
- When switching between light/dark modes, there was a brief flicker that drove me nuts.
- Turns out using
<post>
as an HTML element results in a client-side warning. You don't see it unless you open the Inspector, but that was enough for me to have to change it. - And despite working with a typed language, refactoring isn't always foolproof.
- Furthermore, it turns out I was just trading a client-side warning for a build-time warning, but at least I could fix that.
But the biggest confusion came from server vs client rendering. Despite plenty of literature and videos discussing this in depth, I still fell for it. This manifested in one of two ways:
- Everything worked great locally but broke when deployed on Vercel.
- Everything was working great until I made a change, and suddenly posts didn't load on the home page because it had switched to rendering on the client.
Maybe it's just me, but I actually enjoy the pain of the learning curve phase because I tend to learn the most when I'm outside my comfort zone. All in all, I'd rate the pain of ramping up on TypeScript, React, Next, and Tailwind moderate.
Playground
Now that I had a website up and running, I started feeling the urge to broaden the playing field.
First up: analytics. Do I have any real traffic or content? Absolutely not, but that's not the goal of building this. After trying out several products, I ended up sticking with Simple Analytics, in part due to Pieter's endorsement, and also its the simplicity and privacy-oriented design. But honestly, when it comes to pure analytics, I didn't see a ton of differences between the popular products, so I'd probably just stick with what you're familiar with. Outside of basic analytics, I found Posthog to be great, though hard to really appreciate on such a simple website.
Next up was having a database. Does a static website need one? Absolutely not, but that's not the goal of building this. I ended up using Redis from Upstash to simply store some metadata. Managed services like Upstash, Vercel, Supabase, etc. are great for smaller projects, but at scale, I would just use AWS anyway, so I didn't put much emphasis on the provider.
Once there's enough code, it's time to write some tests. This page does a great job summarizing the options for different scenarios. I ended up going with Vitest for unit tests and Playwright for end-to-end testing.
Logging is yet another thing that's not needed for something like this but would be essential for anything serious, so I ended up rolling with both Winston and Pino. Ultimately, more important than the logging framework is how you access said logs, and unfortunately, all popular services get very expensive very quickly.
Every real application has users, and I'd really prefer to avoid rolling my own authN (or authZ, for that matter) in the future. This one was actually tougher than the rest because there are differences, and it's hard to define or achieve an "optimal" solution. The solutions I looked at were Firebase, Supabase, Auth.js, and Clerk:
- I wanted to stay away from Google services since I'm much more likely to use AWS vs GCP, so Firebase was the first to go.
- I would have probably gone for Supabase for this specific instance (just like I ended up using Upstash), but much less likely in a production environment at scale, so I decided against it (but generally found it pretty great, and Thor is amazing).
- Both Auth.js and Clerk were nearly indentical in terms of technical integration, but the latter provides a lot more on the management front (both components and admin dashboard capabilities).
I ended up going with Clerk, and honestly, if the pricing model scales for your business, I would highly recommend them. It's extremely polished, and the team has several security-focused engineers. The easter egg is on my About page.
Present incarnation
Building this has been fun, and in terms of an "experimental playground," it has met the goal. I'm sure there will be more random things that I will want to dig into. For example, last week I was learning about and experimenting with fonts, and I'm curious to explore more advanced CSS techniques like animations and responsive design. Simultaneously, it has also increased my desire to write a few more posts over the summer (I've learned better than to commit to doing it while I have a demanding job). I'm also considering delving into topics like performance optimization and accessibility. The beauty of a playground like this is that the possibilities are endless, and I'm excited to see where my curiosity takes me next. If you have any suggestions for things I should try to add, or write about, please send them over.
[0]: When I say "meaningful," I quite literally mean "the entire." I've reflected on this many years ago, but it's unfathomable that the main engineer for the sign-up flow when launching one of the biggest strategic investments at Microsoft was someone 2-3 years out of college. I tried to find some screenshots of said flow, but the closest I could get is the start of the sign-up flow. It doesn't take a hyper-creative individual to imagine what that looked like after seeing this.
[1]: When I joined, there wasn't any organizational structure in Engineering, but a small number of engineers were working on the dashboard and website. Frontend engineers had their own habits and routines, which was likely a coping mechanism for being the vast minority of engineers in the company.
[2]: This topic probably warrants its own post.
[3]: My site was hosted for many years on Ghost, and subsequently on Render and deploying Ghost myself, because I was terrified of going back to touching frontend code.