A few months ago, I wrote an article about how the SPA pattern has failed to simplify web development. The SPA pattern (Single-Page Apps), I tried to define, was about the React model, which also covers, to a large extent, the model of Vue, Angular, and other frontend frameworks.
Like any critique, it begs for a prescription and I didn’t give one, other than gesturing toward server-side frameworks like Rails and Django. But I think there are some trends starting to form. I had queued up some time to really dive into the frameworks, but things like walking in parks have taken priority, so here’s just a grand tour.
Primarily I’m talking about Remix, RedwoodJS, and Blitz.js, though I’m sure there are similar efforts in the non-React world that are relevant. Next.js almost falls into this category, but as far as I can tell, it’s still unopinionated about the data layer and most sites that use Next.js are still going to use a separate API stack. But that’s subject to change, because all of these are moving fast.
It’s interesting to note that Remix, Redwood, and Next are all backed by companies or foundations, and that Blitz is aiming early on to be a sponsor-funded project. These projects, I think, are trying to sidestep the “tragedy of the commons” failures of earlier open source, wherein overworked and unpaid maintainers service a large userbase and eventually burn out and abandon the project.
To take Remix as an example, it re-ties data loading with routes, and then gives the pretty amazing promise of no client side data fetching by default. These frameworks are also opinionated about status codes and caching strategies. RedwoodJS automatically creates an ORM-like interface using GraphQL and Prisma.
As context, Remix is backed by the folks from React Training, who are also the folks from React Router, which is as much React pedigree as you can get without joining the team at Facebook. Redwood is run by Preston-Werner Ventures, of Tom Preston-Werner, a GitHub founder. Next.js is sponsored and heavily promoted by Vercel, neé Zeit.
It’s worthwhile to just mention Turbolinks. I didn’t use it until this year, and apparently there were issues with it before, but the pitch for Turbolinks 5 is: what is the bare minimum you need to do to get the SPA experience without any cooperation from your application?
In terms of power-to-weight for user experience improvements, Turbolinks is a standout: it adds very little complexity and a tiny size impact for a big user experience improvement.
How do they do this? Well, a lot of WebSockets, in the case of Reflex and LiveView, as well as very tightly coupled server interactions. As you can see in the LiveView demo, which I highly recommend, these frameworks tend to operate sort of like reactive DOM libraries on the front end – in which the framework figures out minimal steps to transform from one state to another - except those steps are computed on the server side and then generically applied on the client side. They also do a lot more data storage & state management on the server-side, because a lot of those interactions which wouldn’t be persisted to the server are now at least communicated to the server.
These frameworks are exciting, and also extremely contrarian, because they are the polar opposite of the “frontend plus agnostic API layer” pattern, and they also wholeheartedly embrace the thing everyone tries to avoid: mutable state on the server.
The main ones I’ve looked at are Stimulus (out of the Ruby on Rails camp), Alpine, and htmlx. They’re all tiny, and work great in existing pages. I think – and here come the flames – Web Components also fit into this sphere of progressive enhancement! If you just use good web components - only ones that GitHub writes is a good rule of thumb - then they can fit the role of just improving an existing static UI. It’s where you start to use Web Components as an apples-to-apples replacement for full-fledged frontend frameworks is where things seem to get dicey.
These frameworks have the luxury of operating on a deeply improved web stack, one with fundamental components like fetch() and MutationObserver. These things were previously at the core of the utility of progressive enhancement frameworks, and now they can just be the utilities that those frameworks build on.
I’m sure that there are additional patterns out there! But these currents all seem strong right now, and it’s fascinating to see some really divergent and adventurous – and common-sense – approaches start to crop up.