The SPA vacation

I am a “full-stack” developer, which means a lot of things. Specifically for the purposes of this post, if means that I work with every part of a web application, and those are traditionally divided into three layers. First is the front-end or client-side layer: that’s what you see when you visit the site in your browser. That client connects to the back-end or server-side layer to get and store its data and generally accomplish things. Underneath all that, the database keeps track of everything; it tends to be a special snowflake, so we’ll ignore it for the moment.

Client code, as it must run in a browser, uses a mix of three different languages: HTML for markup, CSS for styling, and Javascript for interactivity and communication. That’s already a lot to know, which is why full-stack developers must be more generalist than specialist. (And, incidentally, why I enjoy the field more than a specialized role.) But then you add in the server, which typically uses a fourth language. That can be PHP, as in my current job, or the Python I like to use for my hobby projects, or Ruby, Java, C#, Elixir, or just about anything. Really, it’s hard to find a language that doesn’t have the capability to be used for the serve side.

That includes Javascript, and the past decade has given us the rise of Node, along with a number of web frameworks based on it, such as Express and Nest. The draw for these is simple: if you use the same programming language on the front and back ends, that’s one less thing you need to learn.

JSON the killer

Node is a great thing. I use it on a daily basis, even if I’m not serving web pages with it. But this encroachment of Javascript into the server realm also brought about the rise of the Single Page Application, or SPA. Instead of the traditional web model of the server sending pages and the client (i.e., browser) rendering them, the SPA takes a different approach. Why not let the client do all the work of creating HTML elements?

It’s simple and even ingenious. The server just offers an API, so theoretically anybody could use it, and the client fetches data from that API, creating HTML on the fly based on a response in the form of JSON. (You could also use XML like in the old days, but…no. Let’s not go there.) Perfect separation of concerns, because why should the server care how the data is presented?

And we got some really great tools out of that, along with a whole new way of looking at the web as an application platform. Angular was the first to hit it big, while React and Vue are the top two now. All these share a focus on “reactive” programming, a style of event-driven programming where data changes automatically when its dependencies do. Very simple, fairly elegant, and…

Build me up, buttercup

…And a total mess to build. Angular, React, and Vue all share that problem, and it’s not getting any better. Building a web application using any of these frameworks is black magic, pure and simple. I’ve been doing this for 4 years now, and I still don’t think I’ve even scratched the surface of Webpack configuration. Seriously, I just use Laravel Mix, or copy a Webpack config file from somewhere else, because I’ve got better things to do with my time. Like, you know, writing the code!

The fault lies in the sheer complexity involved, and the fact that modules (one of the most integral parts of any programming environment since around the 1970s) were, in Javascript’s case, kind of bolted on. So we have to assume someone out there is using an old browser that doesn’t support them, or that we’re in an environment where we can’t load them, which means smashing everything into a single file. Oh, and we want to make that file as small as possible, because bandwidth is still an issue. Add in a minification step, then. Want to use that fancy new API Chrome just added? Well, here’s a polyfill for everything else on the market.

There’s a reason why node_modules is a meme. There’s no valid reason why it should be five hundred megabytes for a “starter” React app. Modularization and the borderline insane dependency lists of a web framework have turned the web dev environment into a mess. Not only that, but it’s nearly impossible to get an app built without running multiple processes simultaneously: Webpack, dev server, maybe a CSS postprocessor, and who knows what else. I understand that you do need a lot of tools to cover various use cases, but can’t we think about optimizing the developer’s experience a little, too?

Zero is better than nothing

When I wrote my first web page in 1996, it was easy. You didn’t need any specialized software, just a text editor, a browser, and maybe an FTP client. Sure, the internet has moved on since then, and pages are a lot more complex. They can do so much more that sites I use every day in 2021 would have been unfathomable to anyone 25 years ago, let alone a nerdy 13-year-old.

But does that increased complexity require a proportional increase in development complexity, or can we get back to making quality pages without all the cruft? In other words, can we make modern web applications without a client-side build process?

Until a few days ago, I didn’t think so. Because that was the received wisdom: modern apps are SPAs, and SPAs need a client framework. And that client framework just has to have a build step. Even Preact, the stripped-down cousin to React that boasts of being easy to install, requires a build step for its templates. Tailwind CSS needs one to cut its 1.7 MB of styling into something both manageable and incredibly sleek.

But…what if we didn’t use frameworks? What if we didn’t use JSON? What if the server sent back HTML instead? That’s exactly what I’m doing in the app I’ve been writing at my day job (as an aside, it still feels weird to write that), so what makes the SPA approach superior?

I’m not the only one thinking that. There’s a small but growing minority of web devs pushing for HTML over the wire, which is precisely what it sounds like. There are libraries that take advantage of this, using the abilities of modern Javascript and the browser platform to patch HTML on the fly.

You’re going to have to do the request/response roundtrip anyway, so cutting out the “turn JSON into HTML” step at the end will only save client time. Since that client might be a cheap Chinese Android phone, you want to save its processing power for more important things like actually being usable. AJAX and the DOM let us do some invasive surgery on a page without requiring a full refresh, but still providing for usable URL history. (In other words, we can make the Back button usable, too!) And it’s that much less Javascript for us to write, because we’re not worrying about computed properties or event handlers.

HTML over the wire takes us back to the simpler times of yore, while letting us use the tools we’ve invented in the meantime. From my perspective, that’s great, especially because it means that, in a lot of cases, you can get by with a front-end build process that looks like this:

  1. Add a script tag to your “base” HTML.
  2. That’s it. You’re done!

About as easy as it gets. So that’s why my redesign of Clave is going to use htmx and Alpine instead of a complicated SPA based on Vue. I’ll still have Tailwind for styling, so I don’t get away completely build-less, but you could swap in Bootstrap or Bulma or something and eliminate even that step.

The web is the center of many lives in this fallen time. We should be doing everything in our power to make it easier to contribute, and I think HTML over the wire is one way to do that on the development side. By making it easier to create complex applications, we’ll see more of them.

Most people would go to a spa for a vacation. I’m taking a vacation from the SPA, because the simpler life is so much better.