Tue Apr 06 2021

Productionizing a side project

I've been having a lot of fun lately spicing up one of my side projects with some enhancements that make the experience more like a production app.

A while back, I shared the app I built to triage articles that I come across. There are many apps that do this kind of thing, but this one is mine and I use it every day. I come across a lot of articles I want to read, so to make any progress I add them here and check them off after I read them. I bookmark the ones I want to remember on Pinboard.

I mentioned this app to a friend, showed him the anonymous mode that saves links locally, and was surprised a few months later when he mentioned he was still using it! Over the next weekend I added him as my first non-me user, and wrote a blog post about it.

Tooling

Knowing someone else was using my app made me see it with fresh eyes. I wanted the ability to add new features and live with them for a while before adding them to the "production" app. So I added feature toggles, a way to enable or disable features based on a setting on the server or query parameter. Production feature toggles can be complex and powerful, with the ability to enable features for a certain percentage of your users, but mine are simple. They're client-side only, but they let me try things before rolling them out to the main build. It looks something like this:

URL:

helloitsjoe.github.io/newsletter-links?enable_sparkles=true

feature-toggles.js:

const queryParams = new URLSearchParams(window.location.search);
export const isEnabled = toggle => queryParams.get(toggle);

Anywhere in the app:

if (isEnabled('sparkles')) {
  console.log(✨Sparkles!!✨);
}

I wanted an easy way to toggle these features on and off (and an easy way to confirm my new features weren't visible when I didn't want them to be), so I added a dev bar, a panel that can be shown or hidden with a mouse/touch handler. It currently has checkboxes for my toggles (all three of them).

dev bar with feature toggle checkboxes
Not pretty, but it does the trick.

Next, I added automated builds with GitHub Actions and put protection on my main branch to make myself more disciplined about merging changes (branch protection requires pipelines to pass before merging a PR). I already had a bunch of unit tests, so of course those went in the pipeline too. When the build succeeds and the tests pass, I can merge to the main branch, which kicks off an automatic deploy of the updated app to GitHub Pages. Now I had a CI/CD pipeline 1 .

This is already a fair amount of infrastructure, but this weekend it was time to take it to the next level.

Routing with a static server

Now that I have more than one user, the next feature I wanted to add was a public links page, sort of a bare-bones user profile. I thought it would be neat for users to see each other's links, and be able to share what's on their reading list. Eventually I plan on building this out a little more, maybe with a "recently read" or a favorites list.

I love the simplicity of GitHub Pages. It's nice having everything in one place, from source code to CI pipeline to server. But here's the thing: GitHub Pages only provides a static server. In other words, it serves HTML, CSS, and JavaScript, but doesn't give you a server to write routing logic or store user data. There are plenty of services out there that do, but then I would be done and asleep before midnight. Where's the fun in that??

Given this limitation, step one was figuring out how to fetch and display a user's data when I hit the url with their username:

helloitsjoe.github.io/newsletter-links/joe

While I was looking for a way to handle client-side routing, I came across the spa-github-pages repo. It describes how to set up a 404.html to redirect unhandled routes back to the homepage in a way that would feel like it was handling the routes on the server. Could I use this technique to have user profile pages? I could sure as heck try!

I'm not using a client-side router like react-router, but the idea is the same: if a user hits an endpoint that matches a username (e.g. /joe), I use the 404.html to redirect to the homepage, fetch that user's public links from Firebase and display them without checkboxes (so users can't archive each other's links). I had it working in less than an hour!


The other thing I did this weekend was branch builds. I'm super excited about this, but mostly because it was a fun nerdy problem to solve. I don't really expect you to share my excitement, but come along for the ride!

"What's a branch build," you ask? Well, when I merge a branch into the main branch, the code is automatically tested and deployed to the main GitHub Pages app. But what if I want to share something I'm working on before I merge it to main? I'd like a way to include a branch identifier in the URL to access that branch's version of the app.

There are a couple ways to do this, but the one I settled on (for now at least) is this: When I push changes to a branch, the build pipeline puts the code in a subfolder in the main deploy directory. The folder name includes the branch, so I can access it by putting the branch name in the URL. Here's an example, using the profile-pages branch from above:

Main deploy: main deploy screen

Branch deploy: main deploy screen

I'm using the same 404 method above, it even works along with the username and query params in the path! To be honest I probably won't use this a whole lot, but was super excited I came up with a way to get it working.


So that's it! I'm now able to continue building weird features that nobody else will ever use, and keep the main app lean and clean. Most of these tools won't be useful for other side projects, but that's kind of the point, it's like a meta-side-project. They're fun little puzzles to think through, and none of it would have happened without my old friend User Two!

1: Continuous Integration / Continuous Delivery