Building boring webthings for lazy maintainers
-
- 2025-12-10 * * overengineering, software
I like building “webthings” - and I like that term because it sounds less pretentious than “webservices”. I recently wrote about the little location timeline thingie I made, and that article also contains a bit of text about how I build my webthings. I got a few questions via email and social media - mainly around why I build things the way I do, and why I use the languages and frameworks I do - so I figured I could write a whole article dedicated to that topic. Here we are. Do note, however, that everything that…
Building boring webthings for lazy maintainers
-
- 2025-12-10 * * overengineering, software
I like building “webthings” - and I like that term because it sounds less pretentious than “webservices”. I recently wrote about the little location timeline thingie I made, and that article also contains a bit of text about how I build my webthings. I got a few questions via email and social media - mainly around why I build things the way I do, and why I use the languages and frameworks I do - so I figured I could write a whole article dedicated to that topic. Here we are. Do note, however, that everything that follows is my opinion. I have a lot of experience in doing things the way I do, and I’m happy with it, but it’s absolutely not the only way to approach projects. It’s entirely possible that I’m actually a horrible person who is very wrong on the internet - you should not use this as your only basis for a decision.
One thing about me that even avid readers of my giant wall of texts might not know is that I’m shockingly lazy. I’m also easily annoyed when tech things don’t work the way I want them to. This is especially problematic if I’m building small little webthings for myself and they break - because then I’m super annoyed all the time, but I’m also too lazy to fix them! The solution to that is easy: just build things that don’t break, duh. Okay, I am somewhat kidding. I’m not actually that lazy, and I built more than my fair share of software that just randomly breaks. However, what is true is that I build every webthing to a level I would call “production-grade”. I don’t actually believe in “prototypes”, because “prototypes” always end up in production - and I’m too lazy to rewrite a prototype. But what does my actual approach look like?
You might not need a server.
The first question I always ask myself when starting a new webthing is “does this need a server or can I build this with a static-site-generator”? The smartest thing to avoid having to debug broken things is when there are no things that can break1 - and “modern” static site generators can do a lot. I’ve become a huge fan of Astro in recent years, and that’s not because I particularly like the tech stack, it’s because I can build arbitrary things with relative ease. Astro has this feature that allows you to use placeholders in directory and file names and use a piece of arbitrary JavaScript to generate those path segments and page contents. I’ve built all kinds of spooky things with it: from simple websites like the one you’re reading, more complex websites with fancy galleries and images in multiple sections with categories and tags and all the stuff, to a pricelist for a service I’m reselling that needs to read from a weird upstream XML file and also read from a SQLite database. It’s honestly amazing what you can build with a static site generator if you’re creative enough - and it even works with content that needs to update frequently: nothing is stopping you from running the build in a scheduled CI task or something.
Astro is just one of many static site generators, and it doesn’t matter which project you pick as long as you pick one. The benefits of static site generators are somewhat obvious. You’ll never outperform something like nginx in delivering your website. If your build fails for some reason, your live website won’t be down, it just won’t be updated. It’s even easy to add global replication if you really need to - it’s just a bunch of files, after all. And you could even build your own static site generator if you have a special need2!
If you want to publish something on the internet, your first thought shouldn’t be about using PHP or maybe Python for your server - it should be to not build a server at all3. Static site generators are cool. Use them.
Even if you do, you might not need it for everything.
There are a lot of situations where you actually do need a server and you can’t possibly do everything with a static site generator. I get it. But even if you decided you need some server-side logic, you should ask yourself if the majority of your thing is static - and if so, you should still aim for static build outputs, but enrich them somehow.
You might feel the need for some “dynamic” content where you might not need dynamic content. Let’s say, for example, that you have a personal website with a CV on it and you want to show off how many GitHub stars and PRs your projects get. You might be tempted to build a little server thingie to fetch that stuff from GitHub and display it. But do you need to? It doesn’t really matter if the data isn’t real-time - you can easily do that in a static site generator and just have the site rebuild itself once a day in a scheduled job. Most likely, that’s good enough. If you have a thing that needs to react fast to updated data, maybe you can use a webhook-like system to trigger a rebuild of a static page if you need it. Be creative, you can figure something out.
Some cases do require some always-working server, though. A contact form where you can book meetings based on availability might be a good example. But if that’s the only interactive element on your site, you can still build your site using a SSG, and build a server thingie that only does the contact form. Depending on what you do, there are a lot of interesting ways you can combine static and not-static contents. <iframe>s still exist and can work just fine, especially if you style them and the contents well enough so users don’t even feel the difference. Even if you need more, one could easily add some JavaScript to your static site that fetch()es a piece of HTML and adds it to your static page. If you need even more, libraries like fixi.js or htmx might be great ways to integrate a server-side thingie into your static site thingie.
Even if you need a rich server, you can make it boring.
Sometimes, you do need a rich server. My location timeline is an example here - there’s just no really practicable way to make that not have a server-side component, and pretty much all web content depends on highly dynamic information. Another project, the one I want to talk about here, because you could actually go and read all the sources, is a little tool I made for Team 0%, the group with the goal of clearing all Super Mario Maker courses ever created: a small thingie that provides you with an uncleared level depending on your preferences.
Before I do that, though, I feel like I should finally explain what I mean with “boring” and “lazy”. It’s not about any specific tech stack choice or anything fancy. The words I actually would use are: defensive, maintainable, predictable.
In my level randomizer, there are a lot of external dependencies. I don’t run the “big database” that contains all the uncleared levels. I don’t run the Discord group where the clear requests get posted to. I don’t run the service that provides the level thumbnails. Despite all that, I have had zero unexpected downtime since the service went up two years ago - and that’s not because I’m the most amazing developer on earth - it’s partly because I deliberately built defensive mechanisms around those dependencies. Instead of reading directly from the upstream database, I import all levels into a local database. The import runs in the background, and it’s designed in a way to just keep the current set of levels if the import fails. If the upstream database goes down, the worst thing that happens is that I’ll deliver maybe-cleared levels. The “Mark as Clear” button won’t work if Discord is down, but everything else will be fine. And if the thumbnail server is down, all that happens is that… users won’t see a level thumbnail. Writing these things down like this feels obvious. But you’d be surprised how often things like that are forgotten, and services go down because of it.
Likewise, the codebase probably isn’t what most people would call “boring”. The backend is 100% Rust, which isn’t the first language people usually think about when thinking about backend development, but it’s there for a reason. Writing code in Rust might be slower compared to, let’s say, Node.js, if you’re not super experienced in Rust, but the language itself provides me a lot of benefits. I can actually be somewhat lazy, because I almost never write tests for projects like this. The most I do is writing tests for data parsing, data formatting, and stuff like that. For most things, the strict typesystem itself is more than enough to reduce the potential for bugs. Yes, writing extensive type definitions for everything can feel annoying - but I’ll never get a “Uncaught TypeError: undefined is not a function” in production. I might spend a bit more time initially writing the code, but I get rid of huge classes of bugs and production-edge-cases that I’d have to debug otherwise.
There isn’t really any client-side code that could break in an unexpected way - except if the user is using an outdated browser, at which point I’ll just ask them to stop doing that. Primarily because, as of quite recently, the frontend only ships 1.3 KiB worth of JavaScript, none of which is actually required - the app works just fine without it.
The whole server-side stuff gets built as a static binary, which gets thrown into a container image that also contains the frontend assets. That makes deployment super boring, because I just need to start the image and point my webserver to the right port. It also makes server maintenance boring: I restart my servers when there are kernel updates, and I don’t have to fear that my services won’t come up. If the container image remains unchanged, there’s pretty much never a reason why it should fail to start. There won’t be an “oops, my apt-get update updated Python, so now my app fails”, and there won’t be a “oops, my vendored Ruby gem depends on openssl, and my apt-get update updated it, so now I have to manually gem pristine --all”. It also makes running those services boring - at the time of writing, the level randomizer uses less than 800KiB worth of server memory, and ~0.0001 CPU core. This isn’t a 1GiB Ruby on Rails monster, I can keep this thing running forever without even noticing the resource use.
I want boring and lazy.
I picked the words “boring” and “lazy” deliberately. I know they’re misleading, and I even admitted I have better words. I picked those words because they’re often negatively connotated, but I actually want my web thingies to be boring, and I want to be lazy. I don’t want to spend time debugging my production apps, I want to spend the time creating new things. I don’t want to get pinged on Discord from gamers who want to clear levels but can’t because my app is down, I want to play the game myself.
Sure, this approach isn’t for everyone. If you’re building something that genuinely needs to move fast and break things, or if you’re learning and want to experiment with shiny new frameworks, go for it. I’ve done plenty of that myself. But for the stuff I want to keep running quietly in the background while I do other things? Boring wins. The time I’ve saved not debugging production issues is time I’ve spent building new things, playing games, and writing overly long blog posts like this one4.
There’s a lot of hidden excitement in knowing your little web thingie is just… there. Quietly doing its job. Not demanding attention. Just existing.
If you’re building your own webthings, I’d genuinely encourage you to ask yourself, “what’s the most boring way I could build this?”, and just see if you like it. You might.
Footnotes
Yes, yes, I’m a smartass too. I know that even a static page needs a server to deliver those pages. However, I don’t maintain nginx, and nginx also doesn’t break, so I can just pretend like it doesn’t exist. However, there’s some truth to that: it’s quite easy (and cheap) to deploy static pages onto the internet, so you might get away with not hosting anything on your own. ↩ 1.
Which is a totally reasonable thing to do, btw, and you still get all the benefits: if your custom code fails for some reason, you’re not able to update the deployed contents, but the contents are still up and accessible. This makes fixing your generator a lot less pressing and stressful. It also allows you to use excitingly weird languages to build your static page if you feel inclined. ↩ 1.
If you’re just starting with this whole WebDev thing and you aren’t quite sure how to publish and host it, but you want to host it under your own control as opposed to just posting on social media, contact me. I’ll happily help you out. This is not a joke, it’s a serious offer - even and especially for people who want to focus on creating the contents, not on the technical aspects. Feel free to share this with someone you think could need it. ↩ 1.
Okay, to be fair, this one is actually fairly short compared to what I usually write. ↩