Year 2023 in review

Last year was big for Junat.live: tons of bugs squashed, new features added and an explosive growth in userbase. Sentry was kind enough to sponsor us with a very generous plan to catch errors in production, and I installed Umami to see what features need prioritizing.
Privacy and ease of use have always been core principles in making Junat.live. Junat.live has been open source since day one, meaning anyone can inspect the code. But like many other websites, we have integrated with third-party services, which makes developing way more effortless, and having those services publicly available is just not possible. One of those third-party services includes our analytics service. Umami, a lesser-known analytics engine, was chosen because of its out-of-the-box GDPR compliance. Everything, and I mean everything, you do in Junat.live is anonymous. Things like location are not sent to servers, and that's our promise for years to come.
Speaking of ease of use, in late 2023, the filter option was added, meaning that you can see only trains that are relevant to you. Now you can view trains that depart from station A and stop at station B. You probably know where you're going, so let's streamline the experience and get you where you want to be faster 😉. We now also download less data and cache things more efficiently, meaning more speed and less data usage. You can now also save favorites to access the stations you use faster.
But before we get technical, numbers! Unfortunately, Umami was installed in late September, and the analytics data by Cloudflare could be more precise, so I will keep that private. We still have Google, which, by the way, now shows a nice photo next to Junat.live if you search for it:

In December, we achieved 17 thousand clicks from Google, with a total of 29594 views overall! Of which just over 11 thousand were unique users. Most people use Junat.live on mobile devices and Tikkurila was the most popular station for 2023. The below data is for October–December 2023.

Lets get technical
If you have no programming experience, or haven't worked in the software industry, the text below will probably be hard to read. I'll try to keep the text simple enough, but you have been warned.
The big refactor
If you have ever built a website, you usually pick a way to style things and stick with it. CSS is not the most friendly thing to refactor or write in the first place. Much of the testing becomes manual, no matter how you try to automate things. The mistake was laid out early on in the design phase of the Junat.live saga: use hyped-up tools and experiment a little. It did not stand the test of time. In the autumn of 2023, I realized the styling engine Stitches.js I had chosen had been abandoned, and I contemplated options.
Generally, the way to go about unmaintained software is to ignore it. If it works, do not touch it. After all, Stitches.js got security updates, and the maintainers were transparent about their software's direction. The reason Stitches.js itself became excruciating to develop was it required a small runtime on the browser. That's because many frameworks are shifting towards server-side rendering, including the framework of choice for Junat.live: Next.js. Stitches.js maintainers could have rewritten it to require a build step to compile ahead of time (AOT), but many dynamic features that made the tool appealing in the first place would be lost. Due to AOT compilation being almost like a new product was created from scratch, the maintainers looked for new ones to take ownership of the sinking ship. Nobody who could fill the boots volunteered, and the project was officially abandoned.
Now, if I say that ignoring the ugly parts was an option, why didn't I do exactly that? Since Junat.live is built on Next.js and they introduced their app router, it seemed as if everything would be SSR first and client second, which is often the way to build websites. SPAs get a lot of love, but bots like our beloved Google Bot would rather not. Not only is the site not (as snappily) machine-readable, but you'll also have to bore users with loading states like skeleton UIs or spinners. A well-cached webpage generated AOT (like this blog post you're reading right now) jumps faster at users and requires fewer CPU cycles to render, so the website works like butter even on old Nokias – not that old, mind you. Currently, most pages on Junat.live can't be server-side rendered as they fetch data from Digitraffic that limits requests to IP addresses. Nevertheless, having the option to server-side render pages and components seemed appealing considering the future. We might even move on from Digitraffic and use HSL's open data, which does not measure usage per IP address, and can then create the initial render on the server and hydrate live data after the fact.
Moving from Stitches.js to a more stable option was the way to go. I sat on this decision for weeks since I did not want to do a third rewrite. I chose Tailwind because it has gotten a lot of traction and has a large user base. It also supports design systems, which made the migration that much easier. The design system used for Junat.live is relatively primitive, including fonts, type scales, colors, spacing, and icons. Before I began rewriting, though, it was crucial to write automated tests first. Since most of the components in Junat.live are pure (do not contain side-effects), writing stories using Storybook seemed like a no-brainer. Storybook gives free documentation, an interface to see components without having to mock hard-to-reach states, and even integration tests. I ended up writing more tests than deleting and adding lines.
Tailwind is terse and hard to read, almost like regular expressions in readability if you don't have the proper tooling installed. Nonetheless, I did everything possible to make the code as readable as possible. After all, choosing Tailwind was not my preference as I had not used it before the rewrite and chose it because it's what most people use nowadays to generalize a little.
Onwards on to the next adventure!
Please no.
I got excited about the refactor and decided it was time to complicate things. Complicating things was not the goal but the effect. Junat.live is now deployed on Vercel, but it ran on a distributed cluster of Docker containers for a month or so. Not the way to go, not at all.
I'm constantly learning new things and wanted to learn about container orchestration. I was sick of having to worry about each deployment. Not just a couple of times, changes worked on my machine, but not production. With container orchestration, you get things like A/B-, Blue/Green- and canary deployments for free, and rollbacks are as simple as pushing a button. Learning about container orchestration was a positive experience, and the happy path was fun to drift about. Sure, there were some complications, but nothing a couple of cups of coffee and a few screams could not fix. It was only after I had moved Junat.live to the cluster that the pain would start to kick in.
The container orchestration tool of choice was Nomad, not because it's the cool kid on the block, but because I did not need all the features and thus complexity a behemoth like Kubernetes (k8s for short) would bring. Sure, there are alternatives like Minikube and k3s, but they mostly require the same runtime environment as k8s, and one of the goals was to keep the costs low. I achieved this by deploying four virtual private servers and creating a single server node and three client nodes. Having a single server node is never a good idea because if the server node blows up, there's nothing to take its place. Usually, you'd have at least three server nodes and multiple client nodes, but it was outside the budget for me. You'd be partly right if you guessed that the server node quit.
The four horsemen were hosted by Oracle Cloud, which had hired an intern, Tommy. Tommy decided it was time to delete IP addresses from all shared instances in the UK, and Junat.live would suffer a few days of downtime. I made up Tommy, the intern, but a guess as good as any. How do you just straight up yeet IP addresses out of the window? Sure, they were ephemeral, but at best, they can only rotate, not just straight up stop existing. Oracle Cloud was not doing any favors here, but this mishap would be the least of my worries for weeks to come.
Now that I had configured Nomad and dockerized Junat.live, I had to make it visible. For discovery, at the time, you had to use a third-party reverse proxy or Consul since discovery was not built in. Nowadays, Nomad can act as a standalone reverse proxy, but too little too late. I set up Consul, but after some experimentation, it proved to be a dire effort. Consul is a large tool, sometimes even used with k8s, and the complexity started getting out of hand. The other popular alternative is Traefik, which, like Nomad, is built on Go. It had a nice API and some bizarre quirks I would only learn about later. The problem was that Traefik would not do its single job, which it's supposed to do well. Sometimes, the tool would just respond with a status code 404 and a custom error page. It would do this frequently but not constantly— a recipe for disaster.
At this point, I was sick of having to maintain a cluster and just wanted to get back to developing features, which was when I finally adopted Vercel. Now, there are a few services running on a VPS, like this blog and analytics engine. Deployments are as simple as passing the continuous integration and merging to the main, compared to before, where I had to do those above, push to Docker Hub, and provision the image with Terraform.
After many headaches ensued from our little cloud adventure, having a managed cloud do the grunt work made the development velocity exponential. I would push to production more often, and if anything did something as minor as log to the console, I would know as I had set up Sentry as a last resort. Components would be e2e tested with Storybook, and most logic was covered with unit tests. I squashed some critical bugs like live-updating being restricted to 20 trains, which is now uncapped on our part. There were UX improvements like better error messages and informing of downtime if it were to happen. If Digitraffic components suffer an outage, you can now see the affected components right in Junat.live.
There are huge things planned for 2024, and I can't wait to share them with you.